Repository: marigold-ui/marigold
Pull request #2070 (2022-05-13 13:16:28)

feat: Add `width` prop to fields to control width
Ref #1889

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 90cff8c4ca747436f6723b8df0d511727bb46f97

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/marigold-ui/marigold/new/FIE?filename=.changeset/small-baboons-sniff.md&value=---%0A%22%40marigold%2Fcomponents%22%3A%20patch%0A---%0A%0Afeat%3A%20Add%20%60width%60%20prop%20to%20fields%20to%20control%20width%0A)



Patch:
diff --git a/.changeset/serious-garlics-pretend.md b/.changeset/serious-garlics-pretend.md
new file mode 100644
index 000000000..eac015ef2
--- /dev/null
+++ b/.changeset/serious-garlics-pretend.md
@@ -0,0 +1,13 @@
+---
+"@marigold/docs": major
+"@marigold/components": major
+"@marigold/theme-b2b": major
+"@marigold/theme-core": major
+"@marigold/theme-unicorn": major
+---
+
+refa: `<Tooltip>`
+
+- with arrow pointer yay!
+- allows to change placement
+- uses `useComponentStyles`
diff --git a/.changeset/thin-keys-cheer.md b/.changeset/thin-keys-cheer.md
new file mode 100644
index 000000000..ad52149e3
--- /dev/null
+++ b/.changeset/thin-keys-cheer.md
@@ -0,0 +1,5 @@
+---
+"@marigold/components": minor
+---
+
+feat: Add `width` prop to fields to control width
diff --git a/config/jest/package.json b/config/jest/package.json
index 0f1123f94..ee5a7023f 100644
--- a/config/jest/package.json
+++ b/config/jest/package.json
@@ -22,7 +22,7 @@
   },
   "dependencies": {
     "@babel/core": "7.17.10",
-    "@swc/core": "1.2.120",
+    "@swc/core": "1.2.183",
     "@swc/jest": "0.2.15",
     "@types/jest": "27.4.1",
     "babel-jest": "27.5.1",
diff --git a/config/storybook/package.json b/config/storybook/package.json
index abdafa6d6..726ca9e73 100644
--- a/config/storybook/package.json
+++ b/config/storybook/package.json
@@ -29,7 +29,7 @@
     "@storybook/addon-essentials": "6.4.22",
     "@storybook/addon-interactions": "6.4.22",
     "@storybook/jest": "0.0.10",
-    "@storybook/testing-library": "0.0.9",
+    "@storybook/testing-library": "0.0.11",
     "babel-loader": "8.2.5",
     "find-up": "5.0.0",
     "storybook-addon-themes": "6.1.0",
diff --git a/docs/content/components/checkbox.mdx b/docs/content/components/checkbox.mdx
index ce0903f85..118360b9f 100644
--- a/docs/content/components/checkbox.mdx
+++ b/docs/content/components/checkbox.mdx
@@ -20,12 +20,13 @@ import { Checkbox, CheckboxGroup } from '@marigold/components';
 
 ## Props
 
-| Name               | Type      | Default     | Description                                                                                                        |
-| :----------------- | :-------- | :---------- | :----------------------------------------------------------------------------------------------------------------- |
-| `variant`          | `string`  |             | Use a different _variant_ from theme                                                                               |
-| `size`             | `string`  | `'level-1'` | Use a different _size_ from theme                                                                                  |
-| `error` (optional) | `boolean` | `false`     | If `true`, the checkbox is considered invalid and if set the `errorMessage` is shown instead of the `description`. |
-| ...                |           |             | Yes you can use all regular attributes of `input`!                                                                 |
+| Name                 | Type      | Default     | Description                                                                                                        |
+| :------------------- | :-------- | :---------- | :----------------------------------------------------------------------------------------------------------------- |
+| `variant` (optional) | `string`  |             | Use a different _variant_ from theme                                                                               |
+| `size` (optional)    | `string`  | `'level-1'` | Use a different _size_ from theme                                                                                  |
+| `error` (optional)   | `boolean` | `false`     | If `true`, the checkbox is considered invalid and if set the `errorMessage` is shown instead of the `description`. |
+| `width` (optional)   | `string`  | `100%`      | Control the width of the field.                                                                                    |
+| ...                  |           |             | Yes you can use all regular attributes of `input`!                                                                 |
 
 ## Examples
 
diff --git a/docs/content/components/number-field.mdx b/docs/content/components/number-field.mdx
index 5d75f401c..401483188 100644
--- a/docs/content/components/number-field.mdx
+++ b/docs/content/components/number-field.mdx
@@ -36,6 +36,7 @@ import { NumberField } from '@marigold/components';
 | `readOnly` (optional)      | `boolean`                  | `false` | If `true`, the input is readOnly.                                                                                                                                                    |
 | `onChange` (optional)      | `function`                 |         | A callback function that is called with the input's current `value` when the input value changes.                                                                                    |
 | `formatOptions` (optional) | `Intl.NumberFormatOptions` |         | Apply formatting. See [`Intl.NumberFormatOptions`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat) for available options. |
+| `width` (optional)         | `string`                   | `100%`  | Control the width of the field.                                                                                                                                                      |
 | ...                        |                            |         | Yes you can use all regular attributes of `input`!                                                                                                                                   |
 
 ## Examples
diff --git a/docs/content/components/radio.mdx b/docs/content/components/radio.mdx
index c2a789487..17d31d31e 100644
--- a/docs/content/components/radio.mdx
+++ b/docs/content/components/radio.mdx
@@ -32,12 +32,13 @@ import { Radio } from '@marigold/components';
 
 ## Props
 
-| Name               | Type      | Default     | Description                                                                                                        |
-| :----------------- | :-------- | :---------- | :----------------------------------------------------------------------------------------------------------------- |
-| `variant`          | `string`  |             | Use a different _variant_ from theme                                                                               |
-| `size`             | `string`  | `'level-1'` | Use a different _size_ from theme                                                                                  |
-| `error` (optional) | `boolean` | `false`     | If `true`, the checkbox is considered invalid and if set the `errorMessage` is shown instead of the `description`. |
-| ...                |           |             | Yes you can use all regular attributes of `input`!                                                                 |
+| Name                 | Type      | Default     | Description                                                                                                        |
+| :------------------- | :-------- | :---------- | :----------------------------------------------------------------------------------------------------------------- |
+| `variant` (optional) | `string`  |             | Use a different _variant_ from theme                                                                               |
+| `size` (optional)    | `string`  | `'level-1'` | Use a different _size_ from theme                                                                                  |
+| `error` (optional)   | `boolean` | `false`     | If `true`, the checkbox is considered invalid and if set the `errorMessage` is shown instead of the `description`. |
+| `width` (optional)   | `string`  | `100%`      | Control the width of the field.                                                                                    |
+| ...                  |           |             | Yes you can use all regular attributes of `input`!                                                                 |
 
 ## Examples
 
diff --git a/docs/content/components/select.mdx b/docs/content/components/select.mdx
index 55549af3d..7fbd0ec38 100644
--- a/docs/content/components/select.mdx
+++ b/docs/content/components/select.mdx
@@ -47,6 +47,7 @@ import { Select } from '@marigold/components';
 | `errorMessage` (optional)       | `string`                                                                                                 |                                            |
 | `width` (optional)              | `ResponsiveStyleValue<number⎮string>`                                                                    |                                            |
 | `onSelectionChange` (optional)  | `(key: Key) => any`                                                                                      |                                            |
+| `width` (optional)              | `string`                                                                                                 | `100%`                                     |
 
 ## Examples
 
diff --git a/docs/content/components/slider.mdx b/docs/content/components/slider.mdx
index 79c5c549b..d6743c5ed 100644
--- a/docs/content/components/slider.mdx
+++ b/docs/content/components/slider.mdx
@@ -35,12 +35,12 @@ import { Slider } from '@marigold/components';
 | Property                   | Type                                                                                                                                          | Default | Description                                                     |
 | :------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :-------------------------------------------------------------- |
 | `disabled` (optional)      | `boolean`                                                                                                                                     | `false` | If `true`, the slider is disabled.                              |
-| `width` (optional)         | `string, boolean`                                                                                                                             | `100%`  | The slider's width value.                                       |
 | `value` (optional)         | `number[]`                                                                                                                                    |         | The slider's value (controlled).                                |
 | `defaultValue` (optional)  | `number[]`                                                                                                                                    |         | The slider's default value (uncontrolled).                      |
 | `maxValue` (optional)      | `number`                                                                                                                                      | `100`   | The slider's maximum value.                                     |
 | `step` (optional)          | `number`                                                                                                                                      |         | The slider's step value.                                        |
 | `formatOptions` (optional) | [`Intl.NumberFormatOptions`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat) |         | The slider's step value.                                        |
+| `width` (optional)         | `string`                                                                                                                                      | `100%`  | Control the width of the field.                                 |
 | ...                        |                                                                                                                                               |         | Yes you can use all regular attributes of `input type = range`! |
 
 ## Examples
diff --git a/docs/content/components/switch.mdx b/docs/content/components/switch.mdx
index eba507224..375969910 100644
--- a/docs/content/components/switch.mdx
+++ b/docs/content/components/switch.mdx
@@ -27,12 +27,13 @@ import { Switch } from '@marigold/components';
 
 ## Props
 
-| Property              | Type      | Default |
-| :-------------------- | :-------- | :------ |
+| Property              | Type      | Default | Description                     |
+| :-------------------- | :-------- | :------ | ------------------------------- |
 | `variant` (optional)  | `string`  | ``      |
 | `disabled` (optional) | `boolean` | `false` |
 | `checked` (optional)  | `boolean` | `false` |
 | `size` (optional)     | `string`  | ``      |
+| `width` (optional)    | `string`  | `100%`  | Control the width of the field. |
 
 ## Examples
 
diff --git a/docs/content/components/text-area.mdx b/docs/content/components/text-area.mdx
index c29350cd8..385460ed3 100644
--- a/docs/content/components/text-area.mdx
+++ b/docs/content/components/text-area.mdx
@@ -41,7 +41,8 @@ import { TextArea } from '@marigold/components';
 | `required` (optional)     | `boolean`   | `false` | If `true`, the input is required.                                                                                                        |
 | `readOnly` (optional)     | `boolean`   | `false` | If `true`, the input is readOnly.                                                                                                        |
 | `onChange` (optional)     | `function`  |         | A callback function that is called with the input's current `value` when the input value changes.                                        |
-| others                    |             |         | Yes you can use all regular attributes of `textarea`!                                                                                    |
+| `width` (optional)        | `string`    | `100%`  | Control the width of the field.                                                                                                          |
+| ...                       |             |         | Yes you can use all regular attributes of `textarea`!                                                                                    |
 
 ## Examples
 
diff --git a/docs/content/components/text-field.mdx b/docs/content/components/text-field.mdx
index c46e30ae4..9d1e92b98 100644
--- a/docs/content/components/text-field.mdx
+++ b/docs/content/components/text-field.mdx
@@ -31,6 +31,7 @@ import { TextField } from '@marigold/components';
 | `readOnly` (optional)     | `boolean`   | `false` | If `true`, the input is readOnly.                                                                                                        |
 | `type` (optional)         | `string`    | `text`  | The type of the input field.                                                                                                             |
 | `onChange` (optional)     | `function`  |         | A callback function that is called with the input's current `value` when the input value changes.                                        |
+| `width` (optional)        | `string`    | `100%`  | Control the width of the field.                                                                                                          |
 | ...                       |             |         | Yes you can use all regular attributes of `input`!                                                                                       |
 
 ## Examples
diff --git a/docs/content/components/tooltip.mdx b/docs/content/components/tooltip.mdx
index aac8dd644..ded79e4b1 100644
--- a/docs/content/components/tooltip.mdx
+++ b/docs/content/components/tooltip.mdx
@@ -2,111 +2,63 @@
 title: Tooltip
 ---
 
-import { DoAndDont } from '../../src/components/DoAndDont';
-import {
-  Box,
-  Button,
-  Stack,
-  Tooltip,
-  TooltipTrigger,
-} from '@marigold/components';
-import { Info } from '@marigold/icons';
-
 # Tooltip
 
-The Tooltip should provide extra information to the user. This information should be
-secondary and not escential to perform a task.
-A tooltip consists of two parts: the **trigger** element and the **tooltip** itself.
-Users may reveal the tooltip by hovering or focusing the trigger.
+The `Tooltip` components allows to provide additional information to that user.
 
-<MarigoldTheme>
-  <TooltipTrigger isOpen>
-    <Button variant="" size="" aria-label="infoIconButton">
-      <Info fill="info" />
-    </Button>
-    <Tooltip>Tooltip</Tooltip>
-  </TooltipTrigger>
-</MarigoldTheme>
+```tsx
+<Tooltip.Trigger>
+  <Button variant="primary">Hover me!</Button>
+  <Tooltip>Look at this cool tip!</Tooltip>
+</Tooltip.Trigger>
+```
 
 ```tsx onlyCode
-import { Tooltip, TooltipTrigger } from '@marigold/components';
+import { Tooltip } from '@marigold/components';
 ```
 
 ## Props
 
-| Property             | Type     | Default     |
-| :------------------- | :------- | :---------- |
-| `variant` (optional) | `string` | `__default` |
+### TooltipTrigger
+
+| Property                 | Type                                     | Default    |
+| :----------------------- | :--------------------------------------- | :--------- |
+| `variant` (optional)     | `string`                                 |            |
+| `size` (optional)        | `string`                                 |            |
+| `disabled` (optional)    | `boolean`                                | `false`    |
+| `offset` (optional)      | `number`                                 |            |
+| `crossOffset` (optional) | `number`                                 |            |
+| `delay` (optional)       | `number`                                 |            |
+| `trogger` (optional)     | `'focus''                                | undefined` |
+| `placement` (optional)   | `'top' \| 'bottom' \| 'left' \| 'right'` | `'top'`    |
 
 ## Examples
 
 ### Default Tooltip
 
 ```tsx expandCode
-<TooltipTrigger>
-  <Button variant="" size="" aria-label="infoIconButton">
+<Tooltip.Trigger>
+  <Button>
     <Info fill="info" />
   </Button>
   <Tooltip>Tooltip</Tooltip>
-</TooltipTrigger>
+</Tooltip.Trigger>
 ```
 
-## Dos & Don'ts
+### Tooltip with custom placement
 
-<Stack space="small">
-  <Box
-    display="grid"
-    css={{
-      gridGap: 'small',
-      gridTemplateColumns: 'repeat(auto-fit, minmax(min(500px, 100%), 1fr))',
-    }}
-  >
-    <DoAndDont
-      preview={
-        <TooltipTrigger isOpen>
-          <Button variant="" size="" aria-label="infoIconButton">
-            <Info fill="info" />
-          </Button>
-          <Box as={Tooltip} css={{ position: 'relative' }}>
-            Tooltip
-          </Box>
-        </TooltipTrigger>
-      }
-    >
-      Point an information icon.
-    </DoAndDont>
-    <DoAndDont
-      dont
-      preview={
-        <TooltipTrigger isOpen>
-          <Button variant="" size="" aria-label="infoIconButton">
-            <Info fill="info" />
-          </Button>
-          <Box as={Tooltip} css={{ position: 'relative' }}>
-            Lorem Ipsum is simply dummy text of the printing and typesetting
-            industry.
-          </Box>
-        </TooltipTrigger>
-      }
-    >
-      Don't make extensive explanations on a tool tip. It is only for short and
-      “nice to have” extra information.
-    </DoAndDont>
-    <DoAndDont
-      dont
-      preview={
-        <TooltipTrigger isOpen>
-          <Button variant="" size="" aria-label="infoIconButton">
-            <Info fill="info" />
-          </Button>
-          <Box as={Tooltip} css={{ position: 'relative' }}>
-            If you do not click the box, all data will be deleted.
-          </Box>
-        </TooltipTrigger>
-      }
-    >
-      Don't put highly important information on a tooltip, this element is
-      hidden and should only contain “nice to have” information.
-    </DoAndDont>
-  </Box>
-</Stack>
+```tsx expandCode
+<Tooltip.Trigger placement="right">
+  <Button>Hover me!</Button>
+  <Tooltip>Tooltip</Tooltip>
+</Tooltip.Trigger>
+```
+
+### Tooltip only shows when focused
+
+```tsx expandCode
+<Tooltip.Trigger trigger="focus">
+  <Button>Hover me!</Button>
+  <Tooltip>Tooltip</Tooltip>
+</Tooltip.Trigger>
+```
diff --git a/packages/components/src/Checkbox/Checkbox.stories.tsx b/packages/components/src/Checkbox/Checkbox.stories.tsx
index cbad778dd..8933438c8 100644
--- a/packages/components/src/Checkbox/Checkbox.stories.tsx
+++ b/packages/components/src/Checkbox/Checkbox.stories.tsx
@@ -27,6 +27,12 @@ export default {
       description: 'Option to define an indeterminate state',
       defaultValue: false,
     },
+    width: {
+      control: {
+        type: 'text',
+      },
+      description: 'The width of the field',
+    },
   },
 } as Meta;
 
diff --git a/packages/components/src/Checkbox/Checkbox.tsx b/packages/components/src/Checkbox/Checkbox.tsx
index fc6404e0b..6b7b3d015 100644
--- a/packages/components/src/Checkbox/Checkbox.tsx
+++ b/packages/components/src/Checkbox/Checkbox.tsx
@@ -16,7 +16,7 @@ import {
 } from '@marigold/system';
 import { ComponentProps } from '@marigold/types';
 
-import { useCheckboxGroupContext } from './CheckboxGroup';
+import { useCheckboxGroupContext } from './Context';
 
 // Theme Extension
 // ---------------
@@ -90,10 +90,11 @@ export interface CheckboxProps
   extends ThemeComponentProps,
     Omit<
       ComponentProps<'input'>,
-      'size' | 'type' | 'defaultValue' | CustomCheckboxProps
+      'size' | 'width' | 'type' | 'defaultValue' | CustomCheckboxProps
     >,
     Pick<AriaCheckboxProps, CustomCheckboxProps> {
   children?: ReactNode;
+  width?: string;
   indeterminate?: boolean;
   error?: boolean;
 }
@@ -103,6 +104,7 @@ export interface CheckboxProps
 export const Checkbox = ({
   size,
   variant,
+  width,
   disabled,
   checked,
   defaultChecked,
@@ -186,6 +188,7 @@ export const Checkbox = ({
         alignItems: 'center',
         gap: '1ch',
         position: 'relative',
+        width: width || groupState?.width || '100%',
       }}
       css={styles.container}
       {...hoverProps}
diff --git a/packages/components/src/Checkbox/CheckboxGroup.stories.tsx b/packages/components/src/Checkbox/CheckboxGroup.stories.tsx
index c984e287a..eb63dd961 100644
--- a/packages/components/src/Checkbox/CheckboxGroup.stories.tsx
+++ b/packages/components/src/Checkbox/CheckboxGroup.stories.tsx
@@ -42,6 +42,12 @@ export default {
       description: 'Error state',
       defaultValue: false,
     },
+    width: {
+      control: {
+        type: 'text',
+      },
+      description: 'The width of the group',
+    },
   },
 } as Meta;
 
diff --git a/packages/components/src/Checkbox/CheckboxGroup.tsx b/packages/components/src/Checkbox/CheckboxGroup.tsx
index 6bf28925b..9df8be3d6 100644
--- a/packages/components/src/Checkbox/CheckboxGroup.tsx
+++ b/packages/components/src/Checkbox/CheckboxGroup.tsx
@@ -1,9 +1,6 @@
-import React, { createContext, ReactNode, useContext } from 'react';
+import React, { ReactNode } from 'react';
 import { useCheckboxGroup } from '@react-aria/checkbox';
-import {
-  CheckboxGroupState,
-  useCheckboxGroupState,
-} from '@react-stately/checkbox';
+import { useCheckboxGroupState } from '@react-stately/checkbox';
 import { AriaCheckboxGroupProps } from '@react-types/checkbox';
 
 import {
@@ -14,23 +11,7 @@ import {
 import { ComponentProps } from '@marigold/types';
 
 import { Label } from '../Label';
-
-// Context
-// ---------------
-export interface CheckboxGroupContextProps extends CheckboxGroupState {
-  error?: boolean;
-  variant?: string;
-  size?: string;
-}
-
-/**
- * Needs to be falsy so we can check if a checkbox is used as standalone
- * or in a group.
- */
-export const CheckboxGroupContext = createContext<CheckboxGroupContextProps>(
-  null as any
-);
-export const useCheckboxGroupContext = () => useContext(CheckboxGroupContext);
+import { CheckboxGroupContext } from './Context';
 
 // Theme Extension
 // ---------------
@@ -45,6 +26,7 @@ interface CheckboxGroupProps
   children: ReactNode;
   variant?: string;
   size?: string;
+  width?: string;
   label?: ReactNode;
   required?: boolean;
   disabled?: boolean;
@@ -61,6 +43,7 @@ export const CheckboxGroup = ({
   children,
   variant,
   size,
+  width,
   required,
   disabled,
   readOnly,
@@ -101,7 +84,7 @@ export const CheckboxGroup = ({
         css={styles.group}
       >
         <CheckboxGroupContext.Provider
-          value={{ variant, size, error, ...state }}
+          value={{ variant, size, width, error, ...state }}
         >
           {children}
         </CheckboxGroupContext.Provider>
diff --git a/packages/components/src/Checkbox/Context.ts b/packages/components/src/Checkbox/Context.ts
new file mode 100644
index 000000000..fd21e4943
--- /dev/null
+++ b/packages/components/src/Checkbox/Context.ts
@@ -0,0 +1,18 @@
+import { createContext, useContext } from 'react';
+import { CheckboxGroupState } from '@react-stately/checkbox';
+
+export interface CheckboxGroupContextProps extends CheckboxGroupState {
+  error?: boolean;
+  variant?: string;
+  size?: string;
+  width?: string;
+}
+
+/**
+ * Needs to be falsy so we can check if a checkbox is used as standalone
+ * or in a group.
+ */
+export const CheckboxGroupContext = createContext<CheckboxGroupContextProps>(
+  null as any
+);
+export const useCheckboxGroupContext = () => useContext(CheckboxGroupContext);
diff --git a/packages/components/src/FieldBase/FieldBase.stories.tsx b/packages/components/src/FieldBase/FieldBase.stories.tsx
index 388075e51..c121c9755 100644
--- a/packages/components/src/FieldBase/FieldBase.stories.tsx
+++ b/packages/components/src/FieldBase/FieldBase.stories.tsx
@@ -47,6 +47,12 @@ export default {
       description: 'Whether the help text is an error',
       defaultValue: false,
     },
+    width: {
+      control: {
+        type: 'text',
+      },
+      description: 'The width of the field',
+    },
   },
 } as Meta;
 
diff --git a/packages/components/src/FieldBase/FieldBase.tsx b/packages/components/src/FieldBase/FieldBase.tsx
index 095f70307..63fb6ef15 100644
--- a/packages/components/src/FieldBase/FieldBase.tsx
+++ b/packages/components/src/FieldBase/FieldBase.tsx
@@ -10,6 +10,7 @@ export interface FieldBaseProps {
   children?: ReactNode;
   variant?: string;
   size?: string;
+  width?: string;
   disabled?: boolean;
   required?: boolean;
   label?: ReactNode;
@@ -25,9 +26,10 @@ export interface FieldBaseProps {
 // Component
 // ---------------
 export const FieldBase = ({
+  children,
   variant,
   size,
-  children,
+  width = '100%',
   disabled,
   required,
   label,
@@ -41,7 +43,7 @@ export const FieldBase = ({
 }: FieldBaseProps) => {
   const hasHelpText = !!description || (errorMessage && error);
   return (
-    <Box css={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
+    <Box css={{ display: 'flex', flexDirection: 'column', width }}>
       {label && (
         <Label
           required={required}
diff --git a/packages/components/src/NumberField/NumberField.stories.tsx b/packages/components/src/NumberField/NumberField.stories.tsx
index 3eb11a525..2aecef41d 100644
--- a/packages/components/src/NumberField/NumberField.stories.tsx
+++ b/packages/components/src/NumberField/NumberField.stories.tsx
@@ -57,6 +57,12 @@ export default {
       },
       defaultValue: false,
     },
+    width: {
+      control: {
+        type: 'text',
+      },
+      description: 'The width of the field',
+    },
   },
 } as Meta;
 
diff --git a/packages/components/src/NumberField/NumberField.tsx b/packages/components/src/NumberField/NumberField.tsx
index f694a7531..bdd5c403a 100644
--- a/packages/components/src/NumberField/NumberField.tsx
+++ b/packages/components/src/NumberField/NumberField.tsx
@@ -32,6 +32,7 @@ export interface NumberFieldThemeExtension
  */
 type CustomProps =
   | 'size'
+  | 'width'
   | 'type'
   | 'value'
   | 'defaultValue'
@@ -50,6 +51,7 @@ export interface NumberFieldProps
     Pick<FieldBaseProps, 'label' | 'description' | 'error' | 'errorMessage'> {
   variant?: string;
   size?: string;
+  width?: string;
   hideStepper?: boolean;
 }
 
@@ -58,12 +60,13 @@ export interface NumberFieldProps
 export const NumberField = forwardRef<HTMLInputElement, NumberFieldProps>(
   (
     {
+      variant,
+      size,
+      width,
       disabled,
       required,
       readOnly,
       error,
-      variant,
-      size,
       hideStepper,
       ...rest
     },
@@ -125,6 +128,7 @@ export const NumberField = forwardRef<HTMLInputElement, NumberFieldProps>(
         stateProps={stateProps}
         variant={variant}
         size={size}
+        width={width}
       >
         <Box
           data-group
diff --git a/packages/components/src/Radio/Context.ts b/packages/components/src/Radio/Context.ts
new file mode 100644
index 000000000..de7ae4abd
--- /dev/null
+++ b/packages/components/src/Radio/Context.ts
@@ -0,0 +1,14 @@
+import { createContext, useContext } from 'react';
+import { RadioGroupState } from '@react-stately/radio';
+
+export interface RadioGroupContextProps extends RadioGroupState {
+  variant?: string;
+  size?: string;
+  width?: string;
+  error?: boolean;
+}
+
+export const RadioGroupContext = createContext<RadioGroupContextProps>(
+  null as any
+);
+export const useRadioGroupContext = () => useContext(RadioGroupContext);
diff --git a/packages/components/src/Radio/Radio.stories.tsx b/packages/components/src/Radio/Radio.stories.tsx
index d0a5a874a..333547321 100644
--- a/packages/components/src/Radio/Radio.stories.tsx
+++ b/packages/components/src/Radio/Radio.stories.tsx
@@ -41,6 +41,12 @@ export default {
       description: 'Error',
       defaultValue: false,
     },
+    width: {
+      control: {
+        type: 'text',
+      },
+      description: 'The width of the field',
+    },
   },
 } as Meta;
 
diff --git a/packages/components/src/Radio/Radio.tsx b/packages/components/src/Radio/Radio.tsx
index c06eef8a1..feda52b41 100644
--- a/packages/components/src/Radio/Radio.tsx
+++ b/packages/components/src/Radio/Radio.tsx
@@ -1,4 +1,6 @@
 import React, { useRef } from 'react';
+import { useHover } from '@react-aria/interactions';
+import { useFocusRing } from '@react-aria/focus';
 import { useRadio } from '@react-aria/radio';
 import type { AriaRadioProps } from '@react-types/radio';
 
@@ -13,9 +15,8 @@ import {
 } from '@marigold/system';
 import { ComponentProps } from '@marigold/types';
 
-import { RadioGroup, useRadioGroupContext } from './RadioGroup';
-import { useHover } from '@react-aria/interactions';
-import { useFocusRing } from '@react-aria/focus';
+import { useRadioGroupContext } from './Context';
+import { RadioGroup } from './RadioGroup';
 
 // Theme Extension
 // ---------------
@@ -59,6 +60,10 @@ const Icon = ({ checked, css, ...props }: IconProps) => (
 // Props
 // ---------------
 export type CustomRadioProps =
+  | 'size'
+  | 'width'
+  | 'type'
+  | 'defaultChecked'
   | 'value'
   | 'onFocus'
   | 'onBlur'
@@ -67,18 +72,22 @@ export type CustomRadioProps =
 
 export interface RadioProps
   extends ThemeComponentProps,
-    Omit<
-      ComponentProps<'input'>,
-      'size' | 'type' | 'defaultChecked' | CustomRadioProps
-    >,
+    Omit<ComponentProps<'input'>, CustomRadioProps>,
     AriaRadioProps {
+  width?: string;
   disabled?: boolean;
 }
 
 // Component
 // ---------------
-export const Radio = ({ disabled, ...props }: RadioProps) => {
-  const { variant, size, error, ...state } = useRadioGroupContext();
+export const Radio = ({ width, disabled, ...props }: RadioProps) => {
+  const {
+    variant,
+    size,
+    error,
+    width: groupWidth,
+    ...state
+  } = useRadioGroupContext();
 
   const ref = useRef(null);
   const { inputProps } = useRadio(
@@ -112,6 +121,7 @@ export const Radio = ({ disabled, ...props }: RadioProps) => {
         alignItems: 'center',
         gap: '1ch',
         position: 'relative',
+        width: width || groupWidth || '100%',
       }}
       css={styles.container}
       {...hoverProps}
diff --git a/packages/components/src/Radio/RadioGroup.tsx b/packages/components/src/Radio/RadioGroup.tsx
index da8e7383a..abc0d3811 100644
--- a/packages/components/src/Radio/RadioGroup.tsx
+++ b/packages/components/src/Radio/RadioGroup.tsx
@@ -1,6 +1,6 @@
-import React, { ReactNode, useContext } from 'react';
+import React, { ReactNode } from 'react';
 import { useRadioGroup } from '@react-aria/radio';
-import { RadioGroupState, useRadioGroupState } from '@react-stately/radio';
+import { useRadioGroupState } from '@react-stately/radio';
 import { AriaRadioGroupProps } from '@react-types/radio';
 
 import {
@@ -10,19 +10,7 @@ import {
 } from '@marigold/system';
 
 import { Label } from '../Label';
-
-// Context
-// ---------------
-export interface RadioGroupContextProps extends RadioGroupState {
-  variant?: string;
-  size?: string;
-  error?: boolean;
-}
-
-const RadioGroupContext = React.createContext<RadioGroupContextProps>(
-  null as any
-);
-export const useRadioGroupContext = () => useContext(RadioGroupContext);
+import { RadioGroupContext } from './Context';
 
 // Theme Extension
 // ---------------
@@ -39,6 +27,7 @@ export interface RadioGroupProps
   children: ReactNode[];
   variant?: string;
   size?: string;
+  width?: string;
   required?: boolean;
   disabled?: boolean;
   readOnly?: boolean;
@@ -52,6 +41,7 @@ export const RadioGroup = ({
   orientation = 'vertical',
   size,
   variant,
+  width,
   required,
   disabled,
   readOnly,
@@ -93,7 +83,9 @@ export const RadioGroup = ({
         }}
         css={styles.group}
       >
-        <RadioGroupContext.Provider value={{ variant, size, error, ...state }}>
+        <RadioGroupContext.Provider
+          value={{ variant, size, width, error, ...state }}
+        >
           {children}
         </RadioGroupContext.Provider>
       </Box>
diff --git a/packages/components/src/Select/Select.stories.tsx b/packages/components/src/Select/Select.stories.tsx
index aa3cb8280..133cd3a70 100644
--- a/packages/components/src/Select/Select.stories.tsx
+++ b/packages/components/src/Select/Select.stories.tsx
@@ -52,6 +52,12 @@ export default {
       },
       description: 'Error Message',
     },
+    width: {
+      control: {
+        type: 'text',
+      },
+      description: 'The width of the field',
+    },
   },
 } as Meta;
 
diff --git a/packages/components/src/Select/Select.tsx b/packages/components/src/Select/Select.tsx
index 779dfd953..d1fd7b524 100644
--- a/packages/components/src/Select/Select.tsx
+++ b/packages/components/src/Select/Select.tsx
@@ -65,25 +65,25 @@ export interface SelectProps
       ComponentProps<'select'>,
       'onKeyUp' | 'onKeyDown' | 'onFocus' | 'onBlur' | 'children' | 'size'
     > {
+  variant?: string;
+  size?: string;
+  width?: string;
   open?: boolean;
   disabled?: boolean;
   required?: boolean;
   error?: boolean;
-  variant?: string;
-  size?: string;
-  css?: CSSObject;
 }
 
 // Component
 // ---------------
 export const Select = ({
+  variant,
+  size,
+  width,
   open,
   disabled,
   required,
   error,
-  variant,
-  size,
-  css,
   ...rest
 }: SelectProps) => {
   // Set up i18n
@@ -139,6 +139,7 @@ export const Select = ({
     <FieldBase
       variant={variant}
       size={size}
+      width={width}
       label={props.label}
       labelProps={{ as: 'span', ...labelProps }}
       description={props.description}
diff --git a/packages/components/src/Slider/Slider.stories.tsx b/packages/components/src/Slider/Slider.stories.tsx
index 01f60f27a..9e1d02cd4 100644
--- a/packages/components/src/Slider/Slider.stories.tsx
+++ b/packages/components/src/Slider/Slider.stories.tsx
@@ -16,16 +16,6 @@ export default {
       },
       description: 'Variant to style the Slider',
     },
-    width: {
-      control: {
-        type: 'range',
-        min: 1,
-        max: 1000,
-        step: 100,
-      },
-      defaultValue: 300,
-      description: 'The width size of the slider',
-    },
     disabled: {
       control: {
         type: 'boolean',
@@ -50,6 +40,12 @@ export default {
       defaultValue: 10,
       description: 'The step size of the slider',
     },
+    width: {
+      control: {
+        type: 'text',
+      },
+      description: 'The width of the field',
+    },
   },
 } as Meta;
 
diff --git a/packages/components/src/Slider/Slider.tsx b/packages/components/src/Slider/Slider.tsx
index ea6e30e05..e0d7117cf 100755
--- a/packages/components/src/Slider/Slider.tsx
+++ b/packages/components/src/Slider/Slider.tsx
@@ -33,6 +33,7 @@ export interface SliderProps
       | 'onFocus'
       | 'onBlur'
       | 'size'
+      | 'width'
     >,
     /**
      * `react-aria` has a slightly different API for some events e.g `onChange`, `onFocus`
@@ -44,17 +45,24 @@ export interface SliderProps
     > {
   variant?: string;
   size?: string;
+  width?: string;
   formatOptions?: Intl.NumberFormatOptions;
   children?: ReactNode;
 }
 
+// Component
+// ---------------
 /**
- * Component Slider
  * The slider consists of two parts.
  * A label + the output value and the slider functionality itself.
  * The slider itself consists of a track line and a thumb.
  */
-export const Slider = ({ variant, size, ...props }: SliderProps) => {
+export const Slider = ({
+  variant,
+  size,
+  width = '100%',
+  ...props
+}: SliderProps) => {
   const { formatOptions } = props;
   const trackRef = useRef<HTMLElement>(null);
   const numberFormatter = useNumberFormatter(formatOptions);
@@ -80,6 +88,7 @@ export const Slider = ({ variant, size, ...props }: SliderProps) => {
         display: 'flex',
         flexDirection: 'column',
         touchAction: 'none',
+        width,
       }}
       {...groupProps}
     >
diff --git a/packages/components/src/Switch/Switch.stories.tsx b/packages/components/src/Switch/Switch.stories.tsx
index 8b63f8b91..1d7725d2a 100644
--- a/packages/components/src/Switch/Switch.stories.tsx
+++ b/packages/components/src/Switch/Switch.stories.tsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import type { Meta, ComponentStory } from '@storybook/react';
 import { Switch } from './Switch';
-import { Container } from '../Container';
 
 export default {
   title: 'Components/Switch',
@@ -11,45 +10,36 @@ export default {
         type: 'text',
       },
       description: 'Switch variant style',
-      table: {
-        defaultValue: {
-          summary: '__default',
-        },
-      },
     },
     children: {
       control: {
         type: 'text',
       },
-      description: 'label',
+      description: 'Switch label',
       defaultValue: 'Default Switch',
     },
     size: {
       control: {
         type: 'text',
       },
-      description: 'size',
-      defaultValue: 'Default Size',
+      description: 'Switch size style',
     },
-    disabled: {
+    width: {
       control: {
-        type: 'boolean',
+        type: 'text',
       },
-      description: 'disabled',
-      defaultValue: false,
+      description: 'The width of the field',
     },
-    checked: {
+    disabled: {
       control: {
         type: 'boolean',
       },
-      description: 'checked',
+      description: 'Switch disabled state',
       defaultValue: false,
     },
   },
 } as Meta;
 
 export const Basic: ComponentStory<typeof Switch> = args => (
-  <Container>
-    <Switch {...args} />
-  </Container>
+  <Switch {...args} />
 );
diff --git a/packages/components/src/Switch/Switch.tsx b/packages/components/src/Switch/Switch.tsx
index 385b42c07..0973d5f5a 100755
--- a/packages/components/src/Switch/Switch.tsx
+++ b/packages/components/src/Switch/Switch.tsx
@@ -38,12 +38,14 @@ export interface SwitchProps
   checked?: boolean;
   variant?: string;
   size?: string;
+  width?: string;
 }
 // Component
 // ---------------
 export const Switch = ({
   variant,
   size,
+  width = '100%',
   checked,
   disabled,
   readOnly,
@@ -85,6 +87,7 @@ export const Switch = ({
         justifyContent: 'space-between',
         gap: '1ch',
         position: 'relative',
+        width,
       }}
       css={styles.container}
     >
diff --git a/packages/components/src/TextArea/TextArea.stories.tsx b/packages/components/src/TextArea/TextArea.stories.tsx
index cd5b5a9a3..89cc5c22a 100644
--- a/packages/components/src/TextArea/TextArea.stories.tsx
+++ b/packages/components/src/TextArea/TextArea.stories.tsx
@@ -51,6 +51,12 @@ export default {
       },
       defaultValue: false,
     },
+    width: {
+      control: {
+        type: 'text',
+      },
+      description: 'The width of the field',
+    },
   },
 } as Meta;
 
diff --git a/packages/components/src/TextArea/TextArea.tsx b/packages/components/src/TextArea/TextArea.tsx
index 99f770e91..1a34eb521 100644
--- a/packages/components/src/TextArea/TextArea.tsx
+++ b/packages/components/src/TextArea/TextArea.tsx
@@ -41,12 +41,13 @@ export type CustomTextAreEvents =
 export interface TextAreaProps
   extends Omit<
       ComponentProps<'textarea'>,
-      'value' | 'defaultValue' | CustomTextAreEvents | 'size'
+      'value' | 'defaultValue' | 'size' | CustomTextAreEvents
     >,
     Pick<AriaTextFieldProps, CustomTextAreEvents>,
     Pick<FieldBaseProps, 'label' | 'description' | 'error' | 'errorMessage'> {
   variant?: string;
   size?: string;
+  width?: string;
   value?: string;
   defaultValue?: string;
 }
@@ -54,12 +55,13 @@ export interface TextAreaProps
 // Component
 // ---------------
 export const TextArea = ({
+  variant,
+  size,
+  width,
   disabled,
   required,
   readOnly,
   error,
-  variant,
-  size,
   ...props
 }: TextAreaProps) => {
   const { label, description, errorMessage } = props;
@@ -102,6 +104,7 @@ export const TextArea = ({
       stateProps={stateProps}
       variant={variant}
       size={size}
+      width={width}
     >
       <Box
         as="textarea"
diff --git a/packages/components/src/TextField/TextField.stories.tsx b/packages/components/src/TextField/TextField.stories.tsx
index cb2abbfc4..0563b6643 100644
--- a/packages/components/src/TextField/TextField.stories.tsx
+++ b/packages/components/src/TextField/TextField.stories.tsx
@@ -71,6 +71,12 @@ export default {
       ],
       defaultValue: 'text',
     },
+    width: {
+      control: {
+        type: 'text',
+      },
+      description: 'The width of the field',
+    },
   },
 } as Meta;
 
diff --git a/packages/components/src/TextField/TextField.tsx b/packages/components/src/TextField/TextField.tsx
index eca1dce3c..ae6f9b619 100644
--- a/packages/components/src/TextField/TextField.tsx
+++ b/packages/components/src/TextField/TextField.tsx
@@ -15,7 +15,13 @@ import { Input } from '../Input';
 export interface TextFieldProps
   extends Omit<
       ComponentProps<'input'>,
-      'value' | 'defaultValue' | 'onChange' | 'onFocus' | 'onBlur' | 'size'
+      | 'value'
+      | 'defaultValue'
+      | 'onChange'
+      | 'onFocus'
+      | 'onBlur'
+      | 'size'
+      | 'width'
     >,
     /**
      * `react-aria` has a slightly different API for `onChange`, `onFocus`
@@ -25,6 +31,7 @@ export interface TextFieldProps
     Pick<FieldBaseProps, 'label' | 'description' | 'error' | 'errorMessage'> {
   variant?: string;
   size?: string;
+  width?: string;
   value?: string;
   defaultValue?: string;
 }
@@ -32,12 +39,13 @@ export interface TextFieldProps
 // Component
 // ---------------
 export const TextField = ({
+  variant,
+  size,
+  width,
   disabled,
   required,
   readOnly,
   error,
-  variant,
-  size,
   ...props
 }: TextFieldProps) => {
   const { label, description, errorMessage, autoFocus } = props;
@@ -81,6 +89,7 @@ export const TextField = ({
       stateProps={stateProps}
       variant={variant}
       size={size}
+      width={width}
     >
       <Input
         ref={ref}
diff --git a/packages/components/src/Tooltip/Context.ts b/packages/components/src/Tooltip/Context.ts
new file mode 100644
index 000000000..fc2a6ea0f
--- /dev/null
+++ b/packages/components/src/Tooltip/Context.ts
@@ -0,0 +1,13 @@
+import { createContext, HTMLAttributes, RefObject, useContext } from 'react';
+import { TooltipTriggerState } from '@react-stately/tooltip';
+import { PlacementAxis } from '@react-types/overlays';
+
+export interface TooltipContextProps extends HTMLAttributes<HTMLElement> {
+  state?: TooltipTriggerState;
+  overlayRef?: RefObject<HTMLDivElement>;
+  placement?: PlacementAxis;
+  arrowProps?: HTMLAttributes<HTMLElement>;
+}
+
+export const TooltipContext = createContext<TooltipContextProps>({});
+export const useTooltipContext = () => useContext(TooltipContext);
diff --git a/packages/components/src/Tooltip/Tooltip.stories.tsx b/packages/components/src/Tooltip/Tooltip.stories.tsx
index 51706d81e..4b52c6446 100644
--- a/packages/components/src/Tooltip/Tooltip.stories.tsx
+++ b/packages/components/src/Tooltip/Tooltip.stories.tsx
@@ -1,38 +1,92 @@
 import React from 'react';
 import type { Meta, ComponentStory } from '@storybook/react';
-
-import { Info } from '@marigold/icons';
+import { Box } from '@marigold/system';
 
 import { Tooltip } from './Tooltip';
-import { TooltipTrigger } from './TooltipTrigger';
 import { Button } from '../Button';
 
 export default {
   title: 'Components/Tooltip',
   argTypes: {
-    variant: {
+    disabled: {
+      control: {
+        type: 'boolean',
+      },
+      defaultValue: false,
+      description: 'Disabled state',
+    },
+    offset: {
+      control: {
+        type: 'integer',
+      },
+      description: 'The offset from the trigger element',
+    },
+    crossOffset: {
+      control: {
+        type: 'integer',
+      },
+      description:
+        'The additional offset across the cross acis from the trigger element',
+    },
+    delay: {
+      control: {
+        type: 'number',
+      },
+      description: 'The delay before the tooltip is shown',
+    },
+    trigger: {
+      control: {
+        type: 'select',
+      },
+      options: [undefined, 'focus'],
+      defaultValue: undefined,
+      description: 'The trigger type (default = focus AND hover)',
+    },
+    placement: {
+      control: {
+        type: 'select',
+      },
+      options: ['top', 'bottom', 'left', 'right'],
+      defaultValue: undefined,
+      description: 'The placement of the tooltip',
+    },
+    containerPadding: {
       control: {
-        type: 'text',
+        type: 'number',
       },
-      description: 'variant to style tooltip',
+      description: 'The padding around the tooltip',
     },
-    children: {
+    shouldFlip: {
       control: {
-        type: 'text',
+        type: 'boolean',
       },
-      description: 'Tooltip content',
-      defaultValue: 'Tooltip',
+      description: 'Should the tooltip be automatically be flipped',
     },
   },
 } as Meta;
 
 export const Basic: ComponentStory<typeof Tooltip> = args => {
   return (
-    <TooltipTrigger>
-      <Button variant="" size="" aria-label="infoIconButton">
-        <Info fill="info" />
-      </Button>
-      <Tooltip {...args} />
-    </TooltipTrigger>
+    <Box
+      css={{
+        display: 'flex',
+        gap: 10,
+        pt: 120,
+        width: 'min(100% - 3rem, 60ch)',
+        marginInline: 'auto',
+      }}
+    >
+      <Tooltip.Trigger {...args}>
+        <Button variant="primary">Hover me!</Button>
+        <Tooltip>Look at this tooltip!</Tooltip>
+      </Tooltip.Trigger>
+      <Tooltip.Trigger {...args}>
+        <Button variant="primary">Hover no! Me!</Button>
+        <Tooltip>
+          <div>I am a much more longer tooltip you know!</div>
+          <div>I even have two lines!</div>
+        </Tooltip>
+      </Tooltip.Trigger>
+    </Box>
   );
 };
diff --git a/packages/components/src/Tooltip/Tooltip.tsx b/packages/components/src/Tooltip/Tooltip.tsx
index 455ef3067..8ed926415 100755
--- a/packages/components/src/Tooltip/Tooltip.tsx
+++ b/packages/components/src/Tooltip/Tooltip.tsx
@@ -1,41 +1,66 @@
-import React, { useContext } from 'react';
+import React, { ReactNode } from 'react';
 import { useTooltip } from '@react-aria/tooltip';
-import { mergeProps } from '@react-aria/utils';
-import type { TooltipTriggerProps } from '@react-types/tooltip';
 
-import { Box } from '../Box';
-import { TooltipContext } from './TooltipTrigger';
+import {
+  Box,
+  ThemeExtensionsWithParts,
+  useComponentStyles,
+} from '@marigold/system';
+import { ComponentProps } from '@marigold/types';
+
+import { useTooltipContext } from './Context';
+import { TooltipTrigger } from './TooltipTrigger';
 
 // Theme Extension
 // ---------------
-export interface TooltipThemeExtension<Value> {
-  tooltip?: {
-    [key: string]: Value;
-  };
-}
+export interface TooltipThemeExtension
+  extends ThemeExtensionsWithParts<'Tooltip', ['container', 'arrow']> {}
 
 // Props
 // ---------------
-export interface TooltipProps extends TooltipTriggerProps {
+export interface TooltipProps extends ComponentProps<'div'> {
+  children?: ReactNode;
   variant?: string;
+  size?: string;
 }
 
 // Component
 // ---------------
-export const Tooltip: React.FC<TooltipProps> = ({
-  variant = '',
-  children,
-  ...props
-}) => {
-  const { state, ...tooltipProviderProps } = useContext(TooltipContext);
-  const mergedProps = mergeProps(props, tooltipProviderProps);
-  const { tooltipProps } = useTooltip(mergedProps, state);
+export const Tooltip = ({ children, variant, size }: TooltipProps) => {
+  const { arrowProps, state, overlayRef, placement, ...rest } =
+    useTooltipContext();
+  const { tooltipProps } = useTooltip({}, state);
+
+  const styles = useComponentStyles(
+    'Tooltip',
+    { variant, size },
+    { parts: ['container', 'arrow'] }
+  );
 
   return (
-    <Box position="relative" {...tooltipProps}>
-      <Box position="absolute" variant={`tooltip.${variant}`} {...mergedProps}>
-        {children}
-      </Box>
+    <Box
+      {...tooltipProps}
+      {...rest}
+      ref={overlayRef}
+      css={styles.container}
+      data-placement={placement}
+    >
+      <div>{children}</div>
+      <Box
+        {...arrowProps}
+        __baseCSS={{
+          position: 'absolute',
+          height: 0,
+          width: 0,
+          borderStyle: 'solid',
+          borderLeftColor: 'transparent',
+          borderRightColor: 'transparent',
+          borderBottomColor: 'transparent',
+        }}
+        css={styles.arrow}
+      />
     </Box>
   );
 };
+
+Tooltip.Trigger = TooltipTrigger;
diff --git a/packages/components/src/Tooltip/TooltipTrigger.tsx b/packages/components/src/Tooltip/TooltipTrigger.tsx
index 4b7ac9777..4c65b451a 100644
--- a/packages/components/src/Tooltip/TooltipTrigger.tsx
+++ b/packages/components/src/Tooltip/TooltipTrigger.tsx
@@ -1,45 +1,81 @@
-import React, { Ref, RefObject, useRef } from 'react';
+import React, { ReactElement, useRef } from 'react';
 import { FocusableProvider } from '@react-aria/focus';
+import { useOverlayPosition } from '@react-aria/overlays';
 import { useTooltipTrigger } from '@react-aria/tooltip';
-import {
-  TooltipTriggerState,
-  useTooltipTriggerState,
-} from '@react-stately/tooltip';
+import { useTooltipTriggerState } from '@react-stately/tooltip';
+import { PositionProps } from '@react-types/overlays';
+import { TooltipTriggerProps as AriaTooltipTriggerProps } from '@react-types/tooltip';
+import { TooltipContext } from './Context';
+import { Overlay } from '../Overlay';
 
-// TooltipContext
+// Props
 // ---------------
-export const TooltipContext = React.createContext<{
-  state?: TooltipTriggerState;
-}>({});
+export interface TooltipTriggerProps
+  extends Omit<AriaTooltipTriggerProps, 'isDisabled' | 'isOpen'>,
+    Omit<PositionProps, 'isOpen'> {
+  children: [trigger: ReactElement, menu: ReactElement];
+  disabled?: boolean;
+  open?: boolean;
+}
 
-// TooltipTrigger Component
+// Component
 // ---------------
-export const TooltipTrigger: React.FC = ({ children, ...props }) => {
-  const [trigger, tooltip] = React.Children.toArray(children);
+export const TooltipTrigger = ({
+  disabled,
+  open,
+  delay = 1000,
+  placement = 'top',
+  children,
+  ...rest
+}: TooltipTriggerProps) => {
+  const [tooltipTrigger, tooltip] = React.Children.toArray(children);
+  const props = {
+    ...rest,
+    isDisabled: disabled,
+    isOpen: open,
+    delay,
+    placement,
+  };
+
+  const tooltipTriggerRef = useRef(null);
+  const overlayRef = useRef(null);
+
   const state = useTooltipTriggerState(props);
-  const tooltipTriggerRef = useRef<HTMLElement>();
+
   const { triggerProps, tooltipProps } = useTooltipTrigger(
-    {},
+    props,
     state,
-    tooltipTriggerRef as RefObject<HTMLElement>
+    tooltipTriggerRef
   );
 
+  const {
+    overlayProps: positionProps,
+    placement: overlayPlacement,
+    arrowProps,
+  } = useOverlayPosition({
+    placement: props.placement,
+    targetRef: tooltipTriggerRef,
+    offset: props.offset,
+    crossOffset: props.crossOffset,
+    isOpen: state.isOpen,
+    overlayRef,
+  });
+
   return (
-    <FocusableProvider
-      {...triggerProps}
-      ref={tooltipTriggerRef as Ref<HTMLElement>}
-    >
-      {trigger}
-      {state.isOpen && (
-        <TooltipContext.Provider
-          value={{
-            state,
-            ...tooltipProps,
-          }}
-        >
-          {tooltip}
-        </TooltipContext.Provider>
-      )}
+    <FocusableProvider ref={tooltipTriggerRef} {...triggerProps}>
+      {tooltipTrigger}
+      <TooltipContext.Provider
+        value={{
+          state,
+          overlayRef,
+          arrowProps,
+          placement: overlayPlacement,
+          ...tooltipProps,
+          ...positionProps,
+        }}
+      >
+        <Overlay open={state.isOpen}>{tooltip}</Overlay>
+      </TooltipContext.Provider>
     </FocusableProvider>
   );
 };
diff --git a/packages/components/src/Tooltip/index.ts b/packages/components/src/Tooltip/index.ts
index 8cb0b5104..7594a8f06 100644
--- a/packages/components/src/Tooltip/index.ts
+++ b/packages/components/src/Tooltip/index.ts
@@ -1,2 +1,1 @@
 export * from './Tooltip';
-export * from './TooltipTrigger';
diff --git a/packages/components/src/theme.ts b/packages/components/src/theme.ts
index 6b338f375..24fee963b 100644
--- a/packages/components/src/theme.ts
+++ b/packages/components/src/theme.ts
@@ -36,9 +36,7 @@ import type { SwitchThemeExtension } from './Switch';
 import type { TableThemeExtension } from './Table';
 import type { TextThemeExtension } from './Text';
 import type { TextAreaThemeExtension } from './TextArea';
-
-// Old Styling Solution
-import { type TooltipThemeExtension } from './Tooltip';
+import type { TooltipThemeExtension } from './Tooltip';
 
 interface ComponentStyles
   extends BadgeThemeExtension,
@@ -69,11 +67,9 @@ interface ComponentStyles
     TableThemeExtension,
     TextThemeExtension,
     TextAreaThemeExtension,
+    TooltipThemeExtension,
     UnderlayThemeExtension {}
 
-export interface Theme
-  extends Scales,
-    RootThemeExtension<CSSObject>,
-    TooltipThemeExtension<CSSObject> {
+export interface Theme extends Scales, RootThemeExtension<CSSObject> {
   components: ComponentStyles;
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0f052e05c..d37ad80b7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -108,7 +108,7 @@ importers:
   config/jest:
     specifiers:
       '@babel/core': 7.17.10
-      '@swc/core': 1.2.120
+      '@swc/core': 1.2.183
       '@swc/jest': 0.2.15
       '@types/jest': 27.4.1
       babel-jest: 27.5.1
@@ -122,8 +122,8 @@ importers:
       typescript: 4.6.3
     dependencies:
       '@babel/core': 7.17.10
-      '@swc/core': 1.2.120
-      '@swc/jest': 0.2.15_@swc+core@1.2.120
+      '@swc/core': 1.2.183
+      '@swc/jest': 0.2.15_@swc+core@1.2.183
       '@types/jest': 27.4.1
       babel-jest: 27.5.1_@babel+core@7.17.10
       find-up: 5.0.0
@@ -151,7 +151,7 @@ importers:
       '@storybook/addon-essentials': 6.4.22
       '@storybook/addon-interactions': 6.4.22
       '@storybook/jest': 0.0.10
-      '@storybook/testing-library': 0.0.9
+      '@storybook/testing-library': 0.0.11
       babel-loader: 8.2.5
       find-up: 5.0.0
       react: 17.0.2
@@ -171,7 +171,7 @@ importers:
       '@storybook/addon-essentials': 6.4.22_d5ielqfma3aytfbkci42b5ftku
       '@storybook/addon-interactions': 6.4.22_fdtqczkawcrswx4npptvkurkw4
       '@storybook/jest': 0.0.10_sfoxds7t5ydpegc3knd667wn6m
-      '@storybook/testing-library': 0.0.9_sfoxds7t5ydpegc3knd667wn6m
+      '@storybook/testing-library': 0.0.11_sfoxds7t5ydpegc3knd667wn6m
       babel-loader: 8.2.5_usdhdj5awexcm2e5jtwd44bofa
       find-up: 5.0.0
       storybook-addon-themes: 6.1.0_sfoxds7t5ydpegc3knd667wn6m
@@ -4108,16 +4108,6 @@ packages:
       react-is: 17.0.2
     dev: false
 
-  /@napi-rs/triples/1.1.0:
-    resolution: {integrity: sha512-XQr74QaLeMiqhStEhLn1im9EOMnkypp7MZOwQhGzqp2Weu5eQJbpPxWxixxlYRKWPOmJjsk6qYfYH9kq43yc2w==}
-    dev: false
-
-  /@node-rs/helper/1.3.3:
-    resolution: {integrity: sha512-p4OdfQObGN9YFy5WZaGwlPYICQSe7xZYyXB0sxREmvj1HzGKp5bPg2PlfgfMZEfnjIA882B9ZrnagYzZihIwjA==}
-    dependencies:
-      '@napi-rs/triples': 1.1.0
-    dev: false
-
   /@nodelib/fs.scandir/2.1.5:
     resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
     engines: {node: '>= 8'}
@@ -7356,8 +7346,8 @@ packages:
       util-deprecate: 1.0.2
     dev: false
 
-  /@storybook/testing-library/0.0.9_sfoxds7t5ydpegc3knd667wn6m:
-    resolution: {integrity: sha512-X4/Tk7RryB0tZ/9NLUHYTBXW01zRpf6+IhdhqsVl6WOWRyUaIv3zEhr43gQyZhBe4ZZyt5d90FJC9qWmY1oFKg==}
+  /@storybook/testing-library/0.0.11_sfoxds7t5ydpegc3knd667wn6m:
+    resolution: {integrity: sha512-8KbKx3s1e+uF3oWlPdyXRpZa6xtCsCHtXh1nCTisMA6P5YcSDaCg59NXIOVIQCAwKvjRomlqMJH8JL1WyOzeVg==}
     dependencies:
       '@storybook/client-logger': 6.4.22
       '@storybook/instrumenter': 6.4.22_sfoxds7t5ydpegc3knd667wn6m
@@ -7431,8 +7421,17 @@ packages:
       - '@types/react'
     dev: false
 
-  /@swc/core-android-arm64/1.2.174:
-    resolution: {integrity: sha512-gGXBuY8Zs93eNduyrhm+rB+K/smF535uwmamHB1+7BrkuUtt/Lrk3vDTc4D61GMA/vBOqus9IXKHBc+EFmBbog==}
+  /@swc/core-android-arm-eabi/1.2.183:
+    resolution: {integrity: sha512-DzFR8sokjVESnSXWR/4hLDxPUuvFoTpAHbMEQjj6YrwiXeWQneFudATa0ysyGlszGrmlrT+yL5wrK+gp0M8+Kg==}
+    engines: {node: '>=10'}
+    cpu: [arm]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@swc/core-android-arm64/1.2.183:
+    resolution: {integrity: sha512-LHDSXrtRLYvSbPU0Canp6kcanGbu5IdApvY/2njyLLlhi9GNg+0VJosc14O5sSid5qdg/mMaq4y6u1X6j8CxUw==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [android]
@@ -7440,8 +7439,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-darwin-arm64/1.2.174:
-    resolution: {integrity: sha512-dFxu2o8dR55A+d0Qf/Ai0yKnGjon/UT1hWnBY9tcyvgo7XsiFRB/P83tlbVohqq9N+SDVxdBg9bMAR2i8rDTHg==}
+  /@swc/core-darwin-arm64/1.2.183:
+    resolution: {integrity: sha512-4UZhW/6DzcwFI5MQcGHEsjCDl32Ilfg09TOtr9IO9QK0nADE0Vy6K83ze8iXDIxVnu/jmc2rrX/FwmqRCS7F9w==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [darwin]
@@ -7449,8 +7448,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-darwin-x64/1.2.174:
-    resolution: {integrity: sha512-LsANlfBkqfJobPvfojTbUY9xw0ZwTxTicdwaK6BC5TLmYXVfvxvMpmhsvIe3bNlx4jV1z0IrFaJ0YxDSc1s6vw==}
+  /@swc/core-darwin-x64/1.2.183:
+    resolution: {integrity: sha512-3BYS/4jkZAZbmrxXYeDAljjAbE/TAiEXIDKeGiRZ+nRk0sLNqwO30aWZl34U1CbzuK3gPcGHTEVuChUCoTkWKw==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [darwin]
@@ -7458,8 +7457,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-freebsd-x64/1.2.174:
-    resolution: {integrity: sha512-IG0qc9/qUHmiqC6fqT3yq4iBwNRHN1zYbGpzQm4DU/d7DTwsV7D2A+1McFUKmnUOvBG7zdgg5ee91QxUVOCZTQ==}
+  /@swc/core-freebsd-x64/1.2.183:
+    resolution: {integrity: sha512-9nJqmFZzo2sCcRRZzKxYjEHE9dL3xj0vkELIkfDi7p1C+GpiwZTIh6s39KusnhY89s8igdYWnRyg/3d2FHsuFA==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [freebsd]
@@ -7467,8 +7466,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-arm-gnueabihf/1.2.174:
-    resolution: {integrity: sha512-nK4U8s9++e5AVEhWz3W6V0tUsPPjYJlMsb4ZzZR2qhYe53YBURCznFIhUDwe0ib/f6T773Tn7sr52uemcQBu8A==}
+  /@swc/core-linux-arm-gnueabihf/1.2.183:
+    resolution: {integrity: sha512-Bd0jwn715oqJTVICZiyWx6OYK7qAXZhJbpJLEekpkMfTyveLCnwyou+bwdldGPdqOHb+3q0Cg2eds1P0pXs9Xw==}
     engines: {node: '>=10'}
     cpu: [arm]
     os: [linux]
@@ -7476,8 +7475,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-arm64-gnu/1.2.174:
-    resolution: {integrity: sha512-Tf6Ths472ul9Z4p8uFP7vMJXLmBS2h4PQL/zDj3g3N+9D8hs6+tnnBNrkEm6jXs09gO4bvaN+706QxmZUEgyPg==}
+  /@swc/core-linux-arm64-gnu/1.2.183:
+    resolution: {integrity: sha512-QdQJyfL9nQPYMXdH2pdN6v0M0G8hdEY1g2Cao07CwNVGD0ZHURFY1GFPoyXLkVPVUqyvUPCRZSfC4fTUrKUFeA==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
@@ -7485,8 +7484,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-arm64-musl/1.2.174:
-    resolution: {integrity: sha512-vsQjxyCe3+qRMQSuDlYwmFxbRILIycRC8OB/tBhTJXYuwIh4zvpxc+zvSuERbd4cmMWwM/JtXIlP5grSNjgH6A==}
+  /@swc/core-linux-arm64-musl/1.2.183:
+    resolution: {integrity: sha512-hkDyhwgsK+MPZ47CSjk3VIrabX8xzuQHRAJoPseR/kTJ782ur19Dsun+7tVD8aSgmWjG7nBHYvsdF96jLLim6g==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
@@ -7494,8 +7493,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-x64-gnu/1.2.174:
-    resolution: {integrity: sha512-CFEL2WMIQ1mZvvDguaVGgwan9X7Ah7ctHG8SOa+mFaWXdAyVXOWp8sdNMJbqvwYAv980CENfYioI8yL0z1heWA==}
+  /@swc/core-linux-x64-gnu/1.2.183:
+    resolution: {integrity: sha512-jR6yjQWBm5q9wY4kXfT+3rKHoE9+ugGycaO/yuSeekxvs+dlEGcup0vu/VjfurnbfpAj7O2dnSKH7TE95wMkQg==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
@@ -7503,8 +7502,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-x64-musl/1.2.174:
-    resolution: {integrity: sha512-qulTJ3GYE8gUR0yfIOJVfo8SSl/AgLKAplskIHJu/cP0JtRpHL8B598mfny6SuD+ZabBjUvAz6d8ACL9JdhYTw==}
+  /@swc/core-linux-x64-musl/1.2.183:
+    resolution: {integrity: sha512-4YM3TWishQal3qFsNwWXd47em2zTOShgkxUBm2SOGKtPCMsrfi9MJdesyQH5rCrNWGy1lvo7XpvnN2GSEzYjBQ==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
@@ -7512,8 +7511,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-win32-arm64-msvc/1.2.174:
-    resolution: {integrity: sha512-ZogvH4sOVk3pLguYLlc+3lBlGwJ4WOi375VUqDSb92TirBFPjhX98xDn/Xus1fyRKofSGp1zoAs4w80F6hkX6g==}
+  /@swc/core-win32-arm64-msvc/1.2.183:
+    resolution: {integrity: sha512-mIXR1hPkNL2/v3xEcfbgZJclwMuQxJNVIHUyu6+cb8vUp4rbYoJioEi0qh613c36o9MpHccKZftMMd0buNFWXg==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [win32]
@@ -7521,8 +7520,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-win32-ia32-msvc/1.2.174:
-    resolution: {integrity: sha512-QcnXBs2L0O5BiV61orDxJqZsmk2wPUscTTY4upfW5++eCI6IpDYVyfQmtQ9OdVCXWfVEPrhQKuUxLZEjoKxdNQ==}
+  /@swc/core-win32-ia32-msvc/1.2.183:
+    resolution: {integrity: sha512-NokriuUcwOBdj6k6fpNrFcprv2FoHfyV6ymBaZuk8UXdoRhDFguoTsFxKxKo1oGyqYreuPmaK7NBU/H9SNRxDA==}
     engines: {node: '>=10'}
     cpu: [ia32]
     os: [win32]
@@ -7530,8 +7529,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-win32-x64-msvc/1.2.174:
-    resolution: {integrity: sha512-9aFcbPTFA5jtiTQbOsq+/QN29F/YNpOrhcwsUa2Haqq0+2h71rMiBn7oluUERFQ+/se9J0C00iSC0QXif4pfuQ==}
+  /@swc/core-win32-x64-msvc/1.2.183:
+    resolution: {integrity: sha512-oWiEDrUb4aRRc7y558KonVoFqmGXGKFvOj8ceiz4UVwoQCcwNX3djvjIf3zwkj+DwjKiHPFVwhENaRgMphthrA==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [win32]
@@ -7539,38 +7538,38 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core/1.2.120:
-    resolution: {integrity: sha512-9bSN4ZdDQsAiKEdNT0lv/8o0/70xkGFieq/I4cKdA9dQibAtfagzRyLPkCm54XKqd9NwvI9neAfYydbesyY/nw==}
+  /@swc/core/1.2.183:
+    resolution: {integrity: sha512-RwAq9broU0YqoVJqPpTVxXFAj63+Fx1UWAtBsEqWyJQJc7RunuqNb2vMzp9X+5GeVr6yaa8qE7iKB99gp5enJg==}
     engines: {node: '>=10'}
-    dependencies:
-      '@node-rs/helper': 1.3.3
+    hasBin: true
     optionalDependencies:
-      '@swc/core-android-arm64': 1.2.174
-      '@swc/core-darwin-arm64': 1.2.174
-      '@swc/core-darwin-x64': 1.2.174
-      '@swc/core-freebsd-x64': 1.2.174
-      '@swc/core-linux-arm-gnueabihf': 1.2.174
-      '@swc/core-linux-arm64-gnu': 1.2.174
-      '@swc/core-linux-arm64-musl': 1.2.174
-      '@swc/core-linux-x64-gnu': 1.2.174
-      '@swc/core-linux-x64-musl': 1.2.174
-      '@swc/core-win32-arm64-msvc': 1.2.174
-      '@swc/core-win32-ia32-msvc': 1.2.174
-      '@swc/core-win32-x64-msvc': 1.2.174
+      '@swc/core-android-arm-eabi': 1.2.183
+      '@swc/core-android-arm64': 1.2.183
+      '@swc/core-darwin-arm64': 1.2.183
+      '@swc/core-darwin-x64': 1.2.183
+      '@swc/core-freebsd-x64': 1.2.183
+      '@swc/core-linux-arm-gnueabihf': 1.2.183
+      '@swc/core-linux-arm64-gnu': 1.2.183
+      '@swc/core-linux-arm64-musl': 1.2.183
+      '@swc/core-linux-x64-gnu': 1.2.183
+      '@swc/core-linux-x64-musl': 1.2.183
+      '@swc/core-win32-arm64-msvc': 1.2.183
+      '@swc/core-win32-ia32-msvc': 1.2.183
+      '@swc/core-win32-x64-msvc': 1.2.183
     dev: false
 
   /@swc/helpers/0.3.10:
     resolution: {integrity: sha512-ZCioFJz6NcLAA4zQqN6BORE/SFu2tBdwRU3JXQgLBu6JETvFElAOpaP0HYjWpbnCHFEJ5o6c4wWT8QeLbRoe2w==}
     dev: false
 
-  /@swc/jest/0.2.15_@swc+core@1.2.120:
+  /@swc/jest/0.2.15_@swc+core@1.2.183:
     resolution: {integrity: sha512-Ja+YCVPOGPX/nFnPvPKaWpx2HUwrtuvHP1onHOey1gNGhq8CktQ7xQhCFSuyYBQJiW3WOZ+qMsv3aZk1XUBSOg==}
     engines: {npm: '>= 7.0.0'}
     peerDependencies:
       '@swc/core': '*'
     dependencies:
       '@jest/create-cache-key-function': 27.5.1
-      '@swc/core': 1.2.120
+      '@swc/core': 1.2.183
     dev: false
 
   /@szmarczak/http-timer/1.1.2:
diff --git a/themes/theme-b2b/src/component.ts b/themes/theme-b2b/src/component.ts
deleted file mode 100644
index 7fa8d7888..000000000
--- a/themes/theme-b2b/src/component.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-export const component = {
-  dialog: {
-    __default: {
-      bg: 'background',
-      minWidth: 510,
-      minHeight: 240,
-    },
-    backdrop: {
-      bg: '#00000080',
-    },
-  },
-  label: {
-    above: {
-      fontSize: 'xxsmall',
-      color: 'text',
-    },
-    inline: {
-      fontSize: 'xsmall',
-      display: 'inline-flex',
-      alignItems: 'center',
-    },
-    section: {
-      fontSize: 'small',
-      lineHeight: 'large',
-      color: 'gray50',
-    },
-  },
-  tooltip: {
-    __default: {
-      p: 'xsmall',
-      color: 'gray90',
-      fontSize: 14,
-      border: '1px solid',
-      borderColor: 'blue70',
-      borderRadius: 'large',
-      bg: 'blue10',
-    },
-  },
-  validation: {
-    error: {
-      fontSize: 'xxsmall',
-      color: 'error',
-    },
-  },
-} as const;
diff --git a/themes/theme-b2b/src/components/Tooltip.style.ts b/themes/theme-b2b/src/components/Tooltip.style.ts
new file mode 100644
index 000000000..4e8fc12ec
--- /dev/null
+++ b/themes/theme-b2b/src/components/Tooltip.style.ts
@@ -0,0 +1,92 @@
+import { Theme } from '@marigold/components';
+
+const arrowSize = 8;
+
+export const Tooltip: Theme['components']['Tooltip'] = {
+  base: {
+    container: {
+      fontSize: 'xxsmall',
+      lineHeight: 'small',
+
+      border: '1px solid',
+      borderColor: 'blue70',
+      borderRadius: 'large',
+      bg: 'blue10',
+
+      p: 'xsmall',
+
+      '&[data-placement="top"]': {
+        mb: arrowSize,
+      },
+
+      '&[data-placement="right"]': {
+        ml: arrowSize,
+      },
+
+      '&[data-placement="bottom"]': {
+        mt: arrowSize,
+      },
+
+      '&[data-placement="left"]': {
+        mr: arrowSize,
+      },
+    },
+
+    arrow: {
+      borderWidth: arrowSize,
+      borderTopColor: 'blue70',
+
+      '::after': {
+        content: '""',
+        position: 'absolute',
+        height: 0,
+        width: 0,
+
+        borderStyle: 'solid',
+        borderLeftColor: 'transparent',
+        borderRightColor: 'transparent',
+        borderBottomColor: 'transparent',
+
+        borderWidth: arrowSize - 1,
+        borderTopColor: 'blue10',
+
+        left: `-${arrowSize - 1}px`,
+        // weird but looks better
+        top: `-${arrowSize}.5px`,
+      },
+
+      '[data-placement="right"] &, [data-placement="left"] &': {
+        top: '50%',
+        mt: `-${arrowSize}px`,
+
+        '::after': {
+          // weird but looks better
+          top: `-${arrowSize}.5px`,
+        },
+      },
+
+      '[data-placement="top"] &, [data-placement="bottom"] &': {
+        ml: `-${arrowSize}px`,
+      },
+
+      '[data-placement="top"] & ': {
+        top: '100%',
+      },
+
+      '[data-placement="right"] &': {
+        right: '100%',
+        transform: 'rotate(90deg)',
+      },
+
+      '[data-placement="bottom"] & ': {
+        bottom: '100%',
+        transform: 'rotate(180deg)',
+      },
+
+      '[data-placement="left"] &': {
+        left: '100%',
+        transform: 'rotate(270deg)',
+      },
+    },
+  },
+};
diff --git a/themes/theme-b2b/src/components/index.ts b/themes/theme-b2b/src/components/index.ts
index 0ec56e5a1..05264d19e 100644
--- a/themes/theme-b2b/src/components/index.ts
+++ b/themes/theme-b2b/src/components/index.ts
@@ -22,4 +22,5 @@ export * from './Switch.style';
 export * from './Table.style';
 export * from './Text.style';
 export * from './TextArea.style';
+export * from './Tooltip.style';
 export * from './Underlay.style';
diff --git a/themes/theme-b2b/src/index.ts b/themes/theme-b2b/src/index.ts
index ddac194b4..b5b563747 100644
--- a/themes/theme-b2b/src/index.ts
+++ b/themes/theme-b2b/src/index.ts
@@ -1,7 +1,4 @@
 import { colors } from './colors';
-import { component } from './component';
-
-// NEW STYLES!
 import * as components from './components';
 
 export const webFontUrl = [
@@ -81,9 +78,6 @@ const theme: any = {
     xlarge: 2.5,
     xxlarge: 3,
   },
-  ...component,
-
-  // NEW STYLES!!!
   components,
 } as const;
 
diff --git a/themes/theme-core/src/component.ts b/themes/theme-core/src/component.ts
deleted file mode 100644
index 5d458b64b..000000000
--- a/themes/theme-core/src/component.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { colors } from './colors';
-
-export const component = {
-  dialog: {
-    __default: {
-      bg: '#ffffff',
-      minWidth: '510px',
-      minHeight: '240px',
-    },
-    backdrop: {
-      bg: '#00000080',
-    },
-  },
-  label: {
-    above: {
-      fontSize: 'xxsmall',
-      lineHeight: 'small',
-      color: 'text',
-    },
-    inline: {
-      fontSize: 'xxsmall',
-      lineHeight: 'small',
-      display: 'inline-flex',
-      alignItems: 'center',
-    },
-    section: {
-      fontSize: 'xsmall',
-      lineHeight: '32px',
-      color: colors.gray50,
-    },
-  },
-  tooltip: {
-    __default: {
-      p: 'xsmall',
-      color: 'gray90',
-      fontSize: 14,
-      border: '1px solid',
-      borderColor: 'info',
-      borderRadius: 'large',
-      bg: 'blue10',
-    },
-  },
-  validation: {
-    error: {
-      fontSize: '0.813rem',
-      color: 'error',
-    },
-  },
-} as const;
diff --git a/themes/theme-core/src/index.ts b/themes/theme-core/src/index.ts
index 475fcf49f..70cbc6a05 100644
--- a/themes/theme-core/src/index.ts
+++ b/themes/theme-core/src/index.ts
@@ -1,8 +1,6 @@
-import { type Theme } from '@marigold/components';
-import { colors } from './colors';
-import { component } from './component';
+import type { Theme } from '@marigold/components';
 
-// NEW STYLES!
+import { colors } from './colors';
 import * as components from './components';
 
 const theme: Theme = {
@@ -79,9 +77,6 @@ const theme: Theme = {
     xlarge: 2.5,
     xxlarge: 3,
   },
-
-  ...component,
-  // NEW STYLES!!!
   components,
 } as const;
 
diff --git a/themes/theme-unicorn/src/component.ts b/themes/theme-unicorn/src/component.ts
deleted file mode 100644
index 1f868f127..000000000
--- a/themes/theme-unicorn/src/component.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-export const component = {
-  dialog: {
-    __default: {
-      bg: 'background',
-      minWidth: '400px',
-      minHeight: '400px',
-    },
-    backdrop: {
-      bg: '#00000080',
-    },
-  },
-  label: {
-    above: {
-      fontSize: 'xsmall',
-    },
-    inline: {
-      fontSize: 'xsmall',
-      display: 'inline-flex',
-      alignItems: 'center',
-    },
-    section: {
-      fontSize: 'small',
-      lineHeight: 'large',
-      color: 'gray50',
-    },
-  },
-  tooltip: {
-    __default: {
-      p: 'xsmall',
-      color: 'gray90',
-      border: '1px solid',
-      borderRadius: 'large',
-      bg: 'info',
-    },
-  },
-  validation: {
-    error: {
-      fontSize: 'xsmall',
-      textTransform: 'uppercase',
-      color: 'error',
-    },
-  },
-} as const;
diff --git a/themes/theme-unicorn/src/components/Tooltip.style.ts b/themes/theme-unicorn/src/components/Tooltip.style.ts
new file mode 100644
index 000000000..c9e3ba933
--- /dev/null
+++ b/themes/theme-unicorn/src/components/Tooltip.style.ts
@@ -0,0 +1,66 @@
+import { Theme } from '@marigold/components';
+
+const arrowSize = 6;
+
+export const Tooltip: Theme['components']['Tooltip'] = {
+  base: {
+    container: {
+      color: 'purple10',
+      fontSize: 'xxsmall',
+
+      borderRadius: 'huge',
+      bg: 'purple80',
+
+      p: 'xsmall',
+
+      '&[data-placement="top"]': {
+        mb: arrowSize,
+      },
+
+      '&[data-placement="right"]': {
+        ml: arrowSize,
+      },
+
+      '&[data-placement="bottom"]': {
+        mt: arrowSize,
+      },
+
+      '&[data-placement="left"]': {
+        mr: arrowSize,
+      },
+    },
+
+    arrow: {
+      borderWidth: arrowSize,
+      borderTopColor: 'purple80',
+
+      '[data-placement="right"] &, [data-placement="left"] &': {
+        top: '50%',
+        mt: `-${arrowSize}px`,
+      },
+
+      '[data-placement="top"] &, [data-placement="bottom"] &': {
+        ml: `-${arrowSize}px`,
+      },
+
+      '[data-placement="top"] & ': {
+        top: '100%',
+      },
+
+      '[data-placement="right"] &': {
+        right: '100%',
+        transform: 'rotate(90deg)',
+      },
+
+      '[data-placement="bottom"] & ': {
+        bottom: '100%',
+        transform: 'rotate(180deg)',
+      },
+
+      '[data-placement="left"] &': {
+        left: '100%',
+        transform: 'rotate(270deg)',
+      },
+    },
+  },
+};
diff --git a/themes/theme-unicorn/src/components/index.ts b/themes/theme-unicorn/src/components/index.ts
index 0ec56e5a1..05264d19e 100644
--- a/themes/theme-unicorn/src/components/index.ts
+++ b/themes/theme-unicorn/src/components/index.ts
@@ -22,4 +22,5 @@ export * from './Switch.style';
 export * from './Table.style';
 export * from './Text.style';
 export * from './TextArea.style';
+export * from './Tooltip.style';
 export * from './Underlay.style';
diff --git a/themes/theme-unicorn/src/index.ts b/themes/theme-unicorn/src/index.ts
index 7476f506d..c85a519b0 100644
--- a/themes/theme-unicorn/src/index.ts
+++ b/themes/theme-unicorn/src/index.ts
@@ -1,8 +1,6 @@
-import { type Theme } from '@marigold/components';
-import { colors } from './colors';
-import { component } from './component';
+import type { Theme } from '@marigold/components';
 
-// NEW STYLES!
+import { colors } from './colors';
 import * as components from './components';
 
 export const webFontUrl = [
@@ -56,6 +54,7 @@ const theme: Theme = {
     small: 2,
     medium: 4,
     large: 8,
+    huge: 16,
   },
   fonts: {
     body: 'Arial',
@@ -82,9 +81,6 @@ const theme: Theme = {
     xlarge: 2.5,
     xxlarge: 3,
   },
-  ...component,
-
-  // NEW STYLES!!!
   components,
 } as const;
 


Test patch:
diff --git a/packages/components/src/Checkbox/Checkbox.test.tsx b/packages/components/src/Checkbox/Checkbox.test.tsx
index 4e2003910..a49249d96 100644
--- a/packages/components/src/Checkbox/Checkbox.test.tsx
+++ b/packages/components/src/Checkbox/Checkbox.test.tsx
@@ -20,6 +20,10 @@ const theme = {
     none: 0,
     'small-1': 2,
   },
+  sizes: {
+    none: 0,
+    large: 40,
+  },
   components: {
     Checkbox: {
       base: {
@@ -172,6 +176,32 @@ test('allows styling "error" state via theme', () => {
   expect(checkbox).toHaveStyle(`background: ${theme.colors.red}`);
 });
 
+test('takes full width by default', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <Checkbox data-testid="checkbox">With Label</Checkbox>
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const container = screen.getByTestId('checkbox').parentElement;
+  expect(container).toHaveStyle('width: 100%');
+});
+
+test('allows to set width', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <Checkbox data-testid="checkbox" width="large">
+        With Label
+      </Checkbox>
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const container = screen.getByTestId('checkbox').parentElement;
+  expect(container).toHaveStyle(`width: ${theme.sizes.large}px`);
+});
+
 test('support default checked', () => {
   render(
     <ThemeProvider theme={theme}>
diff --git a/packages/components/src/Checkbox/CheckboxGroup.test.tsx b/packages/components/src/Checkbox/CheckboxGroup.test.tsx
index b79f4600a..ef3193441 100644
--- a/packages/components/src/Checkbox/CheckboxGroup.test.tsx
+++ b/packages/components/src/Checkbox/CheckboxGroup.test.tsx
@@ -16,6 +16,11 @@ const theme = {
     'small-1': 12,
     'large-1': 24,
   },
+  sizes: {
+    none: 0,
+    medium: 40,
+    large: 60,
+  },
   components: {
     Checkbox: {
       base: {
@@ -164,6 +169,52 @@ test('passed down variant and size to checkboxes', () => {
   );
 });
 
+test('passes down "width" to checkboxes', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <CheckboxGroup label="Group of Checkboxes" width="large">
+        <Checkbox value="one" data-testid="one">
+          one
+        </Checkbox>
+        <Checkbox value="two" data-testid="two">
+          two
+        </Checkbox>
+      </CheckboxGroup>
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const oneContainer = screen.getByTestId('one').parentElement;
+  expect(oneContainer).toHaveStyle(`width: ${theme.sizes.large}px`);
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const twoContainer = screen.getByTestId('two').parentElement;
+  expect(twoContainer).toHaveStyle(`width: ${theme.sizes.large}px`);
+});
+
+test('passed down "width" can be locally overriden', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <CheckboxGroup label="Group of Checkboxes" width="large">
+        <Checkbox value="one" data-testid="one">
+          one
+        </Checkbox>
+        <Checkbox value="two" data-testid="two" width="medium">
+          two
+        </Checkbox>
+      </CheckboxGroup>
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const oneContainer = screen.getByTestId('one').parentElement;
+  expect(oneContainer).toHaveStyle(`width: ${theme.sizes.large}px`);
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const twoContainer = screen.getByTestId('two').parentElement;
+  expect(twoContainer).toHaveStyle(`width: ${theme.sizes.medium}px`);
+});
+
 test('passes down "disabled" to checkboxes', () => {
   render(
     <CheckboxGroup label="Group of Checkboxes" disabled>
@@ -232,7 +283,7 @@ test('passes down "error" to checkboxes', () => {
   );
 });
 
-test('constrolled', () => {
+test('controlled', () => {
   const onChange = jest.fn();
   render(
     <CheckboxGroup label="Group of Checkboxes" onChange={onChange}>
diff --git a/packages/components/src/FieldBase/FieldBase.test.tsx b/packages/components/src/FieldBase/FieldBase.test.tsx
index 7131451b1..9c7a08668 100644
--- a/packages/components/src/FieldBase/FieldBase.test.tsx
+++ b/packages/components/src/FieldBase/FieldBase.test.tsx
@@ -14,6 +14,10 @@ const theme = {
   fontSizes: {
     'small-1': 12,
   },
+  sizes: {
+    none: 0,
+    large: 60,
+  },
   components: {
     Label: {
       variant: {
@@ -159,3 +163,31 @@ test('passes down variant and size', () => {
   const helptext = screen.getByText('Description');
   expect(helptext).toHaveStyle(`color: ${theme.colors.blue}`);
 });
+
+test('takes full width by default', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <FieldBase label="Label" description="Description">
+        <input type="text" />
+      </FieldBase>
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const container = screen.getByText('Label').parentElement;
+  expect(container).toHaveStyle('width: 100%');
+});
+
+test('allows to set custom width', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <FieldBase label="Label" description="Description" width="large">
+        <input type="text" />
+      </FieldBase>
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const container = screen.getByText('Label').parentElement;
+  expect(container).toHaveStyle(`width: ${theme.sizes.large}px`);
+});
diff --git a/packages/components/src/NumberField/NumberField.test.tsx b/packages/components/src/NumberField/NumberField.test.tsx
index 3ae2392c8..b1651218a 100644
--- a/packages/components/src/NumberField/NumberField.test.tsx
+++ b/packages/components/src/NumberField/NumberField.test.tsx
@@ -17,6 +17,10 @@ const theme = {
     'small-1': 12,
     'large-1': 24,
   },
+  sizes: {
+    none: 0,
+    huge: 120,
+  },
   components: {
     Label: {
       variant: {
@@ -174,6 +178,18 @@ test('passes down variant and size', () => {
   expect(description).toHaveStyle(`font-size: ${theme.fontSizes['small-1']}px`);
 });
 
+test('allows to set width via prop', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <NumberField data-testid="number-field" label="Label" width="huge" />
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const container = screen.getByText('Label').parentElement;
+  expect(container).toHaveStyle(`width: ${theme.sizes.huge}px`);
+});
+
 test('supports disabled', () => {
   render(<NumberField label="A Label" disabled data-testid="number-field" />);
 
diff --git a/packages/components/src/Radio/Radio.test.tsx b/packages/components/src/Radio/Radio.test.tsx
index d18a1d974..277eb9003 100644
--- a/packages/components/src/Radio/Radio.test.tsx
+++ b/packages/components/src/Radio/Radio.test.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import { fireEvent, render, screen } from '@testing-library/react';
-import { Radio } from '.';
+import { Radio } from './Radio';
 import { ThemeProvider } from '@marigold/system';
 
 const theme = {
@@ -19,6 +19,11 @@ const theme = {
     none: 0,
     'large-1': '9999px',
   },
+  sizes: {
+    none: 0,
+    'large-1': 100,
+    'huge-1': 200,
+  },
   components: {
     Radio: {
       base: {
@@ -204,6 +209,90 @@ test('variant and size styling on radio option', () => {
   expect(radio).toHaveStyle(`height: 32px`);
 });
 
+test('takes full width by default', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <Radio.Group label="With Label">
+        <Radio value="1" data-testid="radio-1">
+          Option 1
+        </Radio>
+        <Radio value="2" data-testid="radio-2">
+          Option 2
+        </Radio>
+      </Radio.Group>
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const containerOne = screen.getByTestId('radio-1').parentElement;
+  expect(containerOne).toHaveStyle('width: 100%');
+});
+
+test('set width via prop', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <Radio.Group label="With Label">
+        <Radio value="1" data-testid="radio-1" width="large-1">
+          Option 1
+        </Radio>
+        <Radio value="2" data-testid="radio-2">
+          Option 2
+        </Radio>
+      </Radio.Group>
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const containerOne = screen.getByTestId('radio-1').parentElement;
+  expect(containerOne).toHaveStyle(`width: ${theme.sizes['large-1']}px`);
+});
+
+test('set width via prop in group', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <Radio.Group label="With Label" width="huge-1">
+        <Radio value="1" data-testid="radio-1">
+          Option 1
+        </Radio>
+        <Radio value="2" data-testid="radio-2">
+          Option 2
+        </Radio>
+      </Radio.Group>
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const containerOne = screen.getByTestId('radio-1').parentElement;
+  expect(containerOne).toHaveStyle(`width: ${theme.sizes['huge-1']}px`);
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const containerTwo = screen.getByTestId('radio-2').parentElement;
+  expect(containerTwo).toHaveStyle(`width: ${theme.sizes['huge-1']}px`);
+});
+
+test('width can be overriden locally', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <Radio.Group label="With Label" width="huge-1">
+        <Radio value="1" data-testid="radio-1" width="large-1">
+          Option 1
+        </Radio>
+        <Radio value="2" data-testid="radio-2">
+          Option 2
+        </Radio>
+      </Radio.Group>
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const containerOne = screen.getByTestId('radio-1').parentElement;
+  expect(containerOne).toHaveStyle(`width: ${theme.sizes['large-1']}px`);
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const containerTwo = screen.getByTestId('radio-2').parentElement;
+  expect(containerTwo).toHaveStyle(`width: ${theme.sizes['huge-1']}px`);
+});
+
 test('allows styling "checked" state via theme', () => {
   render(
     <ThemeProvider theme={theme}>
diff --git a/packages/components/src/Select/Select.test.tsx b/packages/components/src/Select/Select.test.tsx
index 3eba55f3e..220ac8526 100644
--- a/packages/components/src/Select/Select.test.tsx
+++ b/packages/components/src/Select/Select.test.tsx
@@ -21,6 +21,10 @@ const theme = {
     'small-1': 14,
     'medium-1': 18,
   },
+  sizes: {
+    none: 0,
+    huge: 120,
+  },
   components: {
     Label: {
       base: {
@@ -505,6 +509,24 @@ test('supports styling with variants and sizes from theme', () => {
   expect(section).toHaveStyle(`font-size: ${theme.fontSizes['medium-1']}px`);
 });
 
+test('set width via props', () => {
+  render(
+    <OverlayProvider>
+      <ThemeProvider theme={theme}>
+        <Select label="Label" data-testid="select" width="huge">
+          <Select.Option key="one">one</Select.Option>
+          <Select.Option key="two">two</Select.Option>
+        </Select>
+      </ThemeProvider>
+    </OverlayProvider>
+  );
+
+  // We need to query all, since there is also a label in the hidden select
+  // eslint-disable-next-line testing-library/no-node-access
+  const container = screen.getAllByText('Label')[0].parentElement;
+  expect(container).toHaveStyle(`width: ${theme.sizes.huge}px`);
+});
+
 test('supports focus styling for button', () => {
   render(
     <OverlayProvider>
diff --git a/packages/components/src/Slider/Slider.test.tsx b/packages/components/src/Slider/Slider.test.tsx
index 81556e300..dbe29eb20 100644
--- a/packages/components/src/Slider/Slider.test.tsx
+++ b/packages/components/src/Slider/Slider.test.tsx
@@ -10,42 +10,44 @@ const theme = {
     secondary: 'blue',
     disabled: 'gray',
   },
-  ...{
-    components: {
-      Slider: {
-        base: {
-          track: {
-            color: 'green',
-            '&:focus': {
-              bg: 'primary',
-            },
-            '&:checked': {
-              bg: 'secondary',
-            },
-            '&:disabled': {
-              bg: 'disabled',
-            },
+  sizes: {
+    none: 0,
+    large: 120,
+  },
+  components: {
+    Slider: {
+      base: {
+        track: {
+          color: 'green',
+          '&:focus': {
+            bg: 'primary',
+          },
+          '&:checked': {
+            bg: 'secondary',
           },
-          thumb: {
-            color: 'green',
-            '&:focus': {
-              bg: 'primary',
-            },
-            '&:disabled': {
-              bg: 'disabled',
-            },
+          '&:disabled': {
+            bg: 'disabled',
           },
-          label: {
-            color: 'text',
-            fontSize: 'xxsmall',
-            fontWeight: 'body',
+        },
+        thumb: {
+          color: 'green',
+          '&:focus': {
+            bg: 'primary',
           },
-          output: {
-            color: 'text',
-            fontSize: 'xxsmall',
-            fontWeight: 'body',
+          '&:disabled': {
+            bg: 'disabled',
           },
         },
+        label: {
+          color: 'text',
+          fontSize: 'xxsmall',
+          fontWeight: 'body',
+        },
+        output: {
+          color: 'text',
+          fontSize: 'xxsmall',
+          fontWeight: 'body',
+        },
       },
     },
   },
@@ -147,3 +149,25 @@ test('supports formatOptions prop', () => {
   expect(slider).toHaveValue('0.5');
   expect(screen.getByRole(/status/)).toContainHTML('50%');
 });
+
+test('takes full width by default', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <Slider>Percent</Slider>
+    </ThemeProvider>
+  );
+
+  const container = screen.getByRole('group');
+  expect(container).toHaveStyle('width: 100%');
+});
+
+test('allows to set width via prop', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <Slider width="large">Percent</Slider>
+    </ThemeProvider>
+  );
+
+  const container = screen.getByRole('group');
+  expect(container).toHaveStyle(`width: ${theme.sizes.large}px`);
+});
diff --git a/packages/components/src/Switch/Switch.test.tsx b/packages/components/src/Switch/Switch.test.tsx
index 237133822..b417e26a5 100644
--- a/packages/components/src/Switch/Switch.test.tsx
+++ b/packages/components/src/Switch/Switch.test.tsx
@@ -13,6 +13,10 @@ const theme = {
     medium: '16px',
     small: '8px',
   },
+  sizes: {
+    none: 0,
+    large: 120,
+  },
   components: {
     Switch: {
       base: {
@@ -117,6 +121,26 @@ test('supports a size', () => {
   expect(track).toHaveStyle(`padding: ${theme.spaces.medium}px`);
 });
 
+test('takes full width by default', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <Switch>Label</Switch>
+    </ThemeProvider>
+  );
+  const { container } = getSwitchParts();
+  expect(container).toHaveStyle('width: 100%');
+});
+
+test('allows to set width via prop', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <Switch width="large">Label</Switch>
+    </ThemeProvider>
+  );
+  const { container } = getSwitchParts();
+  expect(container).toHaveStyle(`width: ${theme.sizes.large}px`);
+});
+
 test('supports disabled prop', () => {
   render(
     <ThemeProvider theme={theme}>
diff --git a/packages/components/src/TextArea/TextArea.test.tsx b/packages/components/src/TextArea/TextArea.test.tsx
index 1a8569b17..1a35495a9 100644
--- a/packages/components/src/TextArea/TextArea.test.tsx
+++ b/packages/components/src/TextArea/TextArea.test.tsx
@@ -13,6 +13,10 @@ const theme = {
   fontSizes: {
     'small-1': 12,
   },
+  sizes: {
+    none: 0,
+    large: 60,
+  },
   components: {
     Label: {
       variant: {
@@ -104,6 +108,30 @@ test('passes down variant and size', () => {
   expect(description).toHaveStyle(`font-size: ${theme.fontSizes['small-1']}px`);
 });
 
+test('takes full width by default', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <TextArea data-testid="text-area" label="Label" />
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const container = screen.getByText('Label').parentElement;
+  expect(container).toHaveStyle('width: 100%');
+});
+
+test('allows to set custom width', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <TextArea data-testid="text-area" label="Label" width="large" />
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const container = screen.getByText('Label').parentElement;
+  expect(container).toHaveStyle(`width: ${theme.sizes.large}px`);
+});
+
 test('supports disabled', () => {
   render(<TextArea label="A Label" disabled data-testid="textarea" />);
 
diff --git a/packages/components/src/TextField/TextField.test.tsx b/packages/components/src/TextField/TextField.test.tsx
index 8ce2cf10c..a447c8949 100644
--- a/packages/components/src/TextField/TextField.test.tsx
+++ b/packages/components/src/TextField/TextField.test.tsx
@@ -13,6 +13,10 @@ const theme = {
   fontSizes: {
     'small-1': 12,
   },
+  sizes: {
+    none: 0,
+    large: 140,
+  },
   components: {
     Label: {
       variant: {
@@ -112,6 +116,30 @@ test('passes down variant and size', () => {
   expect(description).toHaveStyle(`font-size: ${theme.fontSizes['small-1']}px`);
 });
 
+test('takes full width by default', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <TextField label="Label" />
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const container = screen.getByText('Label').parentElement;
+  expect(container).toHaveStyle('width: 100%');
+});
+
+test('allows to set custom width', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <TextField label="Label" width="large" />
+    </ThemeProvider>
+  );
+
+  // eslint-disable-next-line testing-library/no-node-access
+  const container = screen.getByText('Label').parentElement;
+  expect(container).toHaveStyle(`width: ${theme.sizes.large}px`);
+});
+
 test('supports disabled', () => {
   render(<TextField label="A Label" disabled data-testid="text-field" />);
 
diff --git a/packages/components/src/Tooltip/Tooltip.test.tsx b/packages/components/src/Tooltip/Tooltip.test.tsx
index fa48c671a..6d88a5fb3 100644
--- a/packages/components/src/Tooltip/Tooltip.test.tsx
+++ b/packages/components/src/Tooltip/Tooltip.test.tsx
@@ -1,74 +1,194 @@
 import React from 'react';
-import { render, screen } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
 import { ThemeProvider } from '@marigold/system';
+
+import { Button } from '../Button';
 import { Tooltip } from './Tooltip';
-import { TooltipTrigger } from '.';
-import { Button } from '..';
 
 const theme = {
+  fontSizes: {
+    'small-1': '12px',
+    'medium-1': '16px',
+  },
   colors: {
-    primary: 'hotpink',
-    secondary: 'green',
+    green: '#69db7c',
+    lime: '#94d82d',
   },
-  tooltip: {
-    __default: {
-      bg: 'primary',
-    },
-    styled: {
-      bg: 'secondary',
+  components: {
+    Tooltip: {
+      base: {
+        container: {
+          fontSize: 'small-1',
+          bg: 'green',
+        },
+        arrow: {
+          borderTopColor: 'green',
+        },
+      },
+      variant: {
+        lime: {
+          container: {
+            bg: 'lime',
+          },
+        },
+      },
+      size: {
+        medium: {
+          container: {
+            fontSize: 'medium-1',
+          },
+        },
+      },
     },
   },
 };
 
-test('supports default variant', async () => {
+beforeEach(() => {
+  // by firing an event at the beginning of each test, we can put ourselves into
+  // keyboard modality for the test
+  fireEvent.keyDown(document.body, { key: 'Tab' });
+  fireEvent.keyUp(document.body, { key: 'Tab' });
+});
+
+test('does not render tooltip by default', () => {
+  render(
+    <Tooltip.Trigger>
+      <Button>Button!</Button>
+      <Tooltip>Look at this tooltip!</Tooltip>
+    </Tooltip.Trigger>
+  );
+
+  expect(screen.queryByText('Look at this tooltip!')).toBeNull();
+});
+
+test('shows tooltip on focus', () => {
+  render(
+    <Tooltip.Trigger>
+      <Button>Button!</Button>
+      <Tooltip>Look at this tooltip!</Tooltip>
+    </Tooltip.Trigger>
+  );
+
+  const button = screen.getByText('Button!');
+  fireEvent.focus(button);
+
+  expect(screen.getByRole('tooltip')).toBeVisible();
+
+  fireEvent.blur(button);
+
+  expect(screen.queryByText('Look at this tooltip!')).toBeNull();
+});
+
+test('shows tooltip on hover', async () => {
+  render(
+    <Tooltip.Trigger delay={0}>
+      <Button>Button!</Button>
+      <Tooltip>Look at this tooltip!</Tooltip>
+    </Tooltip.Trigger>
+  );
+  // Switch to "mouse mode"
+  fireEvent.mouseDown(document.body);
+  fireEvent.mouseUp(document.body);
+
+  const button = screen.getByText('Button!');
+
+  fireEvent.mouseEnter(button);
+  fireEvent.mouseMove(button);
+
+  expect(screen.getByRole('tooltip')).toBeVisible();
+
+  fireEvent.mouseLeave(button);
+
+  await waitFor(() =>
+    expect(screen.queryByText('Look at this tooltip!')).toBeNull()
+  );
+});
+
+test('can be disabled', () => {
+  render(
+    <Tooltip.Trigger disabled>
+      <Button>Button!</Button>
+      <Tooltip>Look at this tooltip!</Tooltip>
+    </Tooltip.Trigger>
+  );
+
+  const button = screen.getByText('Button!');
+  fireEvent.focus(button);
+
+  expect(screen.queryByText('Look at this tooltip!')).toBeNull();
+});
+
+test('can be opened programatically', () => {
+  render(
+    <Tooltip.Trigger open>
+      <Button>Button!</Button>
+      <Tooltip>Look at this tooltip!</Tooltip>
+    </Tooltip.Trigger>
+  );
+  expect(screen.queryByText('Look at this tooltip!')).toBeVisible();
+});
+
+test('allows to change tooltip placement', () => {
+  // Note: There is no real way to test this without actually rendering the tooltip
+  render(
+    <Tooltip.Trigger placement="bottom">
+      <Button>Button!</Button>
+      <Tooltip>Look at this tooltip!</Tooltip>
+    </Tooltip.Trigger>
+  );
+
+  const button = screen.getByText('Button!');
+  fireEvent.focus(button);
+
+  expect(screen.getByRole('tooltip')).toBeVisible();
+});
+
+test('styled via "Tooltip" from theme', () => {
   render(
     <ThemeProvider theme={theme}>
-      <TooltipTrigger>
-        <Button data-testid="button">Info</Button>
-        <Tooltip data-testid="tooltip">Tooltip</Tooltip>
-      </TooltipTrigger>
+      <Tooltip.Trigger open>
+        <Button>Button!</Button>
+        <Tooltip>Look at this tooltip!</Tooltip>
+      </Tooltip.Trigger>
     </ThemeProvider>
   );
-  const button = screen.getByTestId('button');
-  button.focus();
-  expect(button).toHaveFocus();
 
-  const tooltip = screen.getAllByTestId(/tooltip/);
-  expect(tooltip[1]).toHaveStyle(`background-color: hotpink`);
+  const tooltip = screen.getByRole('tooltip');
+  expect(tooltip).toHaveStyle({
+    fontSize: theme.fontSizes['small-1'],
+    backgroundColor: theme.colors.green,
+  });
 });
 
-test('supports other variant than default', async () => {
+test('accepts variant and size', () => {
   render(
     <ThemeProvider theme={theme}>
-      <TooltipTrigger>
-        <Button data-testid="button">Info</Button>
-        <Tooltip variant="styled" data-testid="tooltip">
-          Tooltip
+      <Tooltip.Trigger open>
+        <Button>Button!</Button>
+        <Tooltip variant="lime" size="medium">
+          Look at this tooltip!
         </Tooltip>
-      </TooltipTrigger>
+      </Tooltip.Trigger>
     </ThemeProvider>
   );
-  const button = screen.getByTestId('button');
-  button.focus();
-  expect(button).toHaveFocus();
 
-  const tooltip = screen.getAllByTestId(/tooltip/);
-  expect(tooltip[1]).toHaveStyle(`background-color: green`);
+  const tooltip = screen.getByRole('tooltip');
+  expect(tooltip).toHaveStyle({
+    fontSize: theme.fontSizes['medium-1'],
+    backgroundColor: theme.colors.lime,
+  });
 });
 
-test('shows Tooltip', async () => {
+test('sets placement as data attribute for styling', () => {
   render(
     <ThemeProvider theme={theme}>
-      <TooltipTrigger>
-        <Button data-testid="button">Info</Button>
-        <Tooltip>Tooltip</Tooltip>
-      </TooltipTrigger>
+      <Tooltip.Trigger open placement="left">
+        <Button>Button!</Button>
+        <Tooltip>Look at this tooltip!</Tooltip>
+      </Tooltip.Trigger>
     </ThemeProvider>
   );
-  const button = screen.getByTestId('button');
-  button.focus();
-  expect(button).toHaveFocus();
 
-  const tooltip = screen.getByText(/Tooltip/);
-  expect(tooltip).toBeDefined();
+  const tooltip = screen.getByRole('tooltip');
+  expect(tooltip).toHaveAttribute('data-placement', 'left');
 });