diff --git a/docs/manifest.json b/docs/manifest.json
index 8454aa294683bf..7691b8dcf9c319 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -1067,6 +1067,12 @@
"markdown_source": "../packages/components/src/snackbar/README.md",
"parent": "components"
},
+ {
+ "title": "Spacer",
+ "slug": "spacer",
+ "markdown_source": "../packages/components/src/spacer/README.md",
+ "parent": "components"
+ },
{
"title": "Spinner",
"slug": "spinner",
diff --git a/packages/components/src/index.js b/packages/components/src/index.js
index afbb7e37db8ae2..3539a38860cddc 100644
--- a/packages/components/src/index.js
+++ b/packages/components/src/index.js
@@ -102,6 +102,7 @@ export { default as SandBox } from './sandbox';
export { default as SelectControl } from './select-control';
export { default as Snackbar } from './snackbar';
export { default as SnackbarList } from './snackbar/list';
+export { Spacer as __experimentalSpacer } from './spacer';
export { default as Spinner } from './spinner';
export { default as TabPanel } from './tab-panel';
export { Text as __experimentalText } from './text';
diff --git a/packages/components/src/spacer/README.md b/packages/components/src/spacer/README.md
new file mode 100644
index 00000000000000..1aad9670c082f5
--- /dev/null
+++ b/packages/components/src/spacer/README.md
@@ -0,0 +1,118 @@
+# Spacer
+
+> **Experimental!**
+
+`Spacer` is a primitive layout component that providers inner (`padding`) or outer (`margin`) space in-between components. It can also be used to adaptively provide space within an `HStack` or `VStack`.
+
+## Table of contents
+
+## Usage
+
+`Spacer` comes with a bunch of shorthand props to adjust `margin` and `padding`. The values of these props work as a multiplier to the library's grid system (base of `4px`).
+
+```jsx
+import {
+ __experimentalSpacer as Spacer,
+ __experimentalHeading as Heading,
+ __experimentalView as View
+} from '@wordpress/components';
+
+function Example() {
+ return (
+
+
+ WordPress.org
+
+
+ Code is Poetry
+
+
+ );
+}
+```
+
+## Props
+
+##### margin
+
+**Type**: `number`
+
+Adjusts all margins.
+
+##### marginBottom
+
+**Type**: `number`
+
+Adjusts bottom margins.
+
+##### marginLeft
+
+**Type**: `number`
+
+Adjusts left margins.
+
+##### marginRight
+
+**Type**: `number`
+
+Adjusts right margins.
+
+##### marginTop
+
+**Type**: `number`
+
+Adjusts top margins.
+
+##### marginX
+
+**Type**: `number`
+
+Adjusts left and right margins.
+
+##### marginY
+
+**Type**: `number`
+
+Adjusts top and bottom margins.
+
+##### padding
+
+**Type**: `number`
+
+Adjusts all padding.
+
+##### paddingBottom
+
+**Type**: `number`
+
+Adjusts bottom padding.
+
+##### paddingLeft
+
+**Type**: `number`
+
+Adjusts left padding.
+
+##### paddingRight
+
+**Type**: `number`
+
+Adjusts right padding.
+
+##### paddingTop
+
+**Type**: `number`
+
+Adjusts top padding.
+
+##### paddingX
+
+**Type**: `number`
+
+Adjusts left and right padding.
+
+##### paddingY
+
+**Type**: `number`
+
+Adjusts top and bottom padding.
diff --git a/packages/components/src/spacer/component.ts b/packages/components/src/spacer/component.ts
new file mode 100644
index 00000000000000..162029e1cb9c66
--- /dev/null
+++ b/packages/components/src/spacer/component.ts
@@ -0,0 +1,36 @@
+/**
+ * Internal dependencies
+ */
+import { createComponent } from '../ui/utils';
+import { useSpacer } from './hook';
+
+/**
+ * `Spacer` is a primitive layout component that providers inner (`padding`) or outer (`margin`) space in-between components. It can also be used to adaptively provide space within an `HStack` or `VStack`.
+ *
+ * `Spacer` comes with a bunch of shorthand props to adjust `margin` and `padding`. The values of these props work as a multiplier to the library's grid system (base of `4px`).
+ *
+ * @example
+ * ```jsx
+ * import { Spacer } from `@wordpress/components`
+ *
+ * function Example() {
+ * return (
+ *
+ *
+ * WordPress.org
+ *
+ *
+ * Code is Poetry
+ *
+ *
+ * );
+ * }
+ * ```
+ */
+const Spacer = createComponent( {
+ as: 'div',
+ useHook: useSpacer,
+ name: 'Spacer',
+} );
+
+export default Spacer;
diff --git a/packages/components/src/spacer/hook.ts b/packages/components/src/spacer/hook.ts
new file mode 100644
index 00000000000000..cd2465627d31c8
--- /dev/null
+++ b/packages/components/src/spacer/hook.ts
@@ -0,0 +1,163 @@
+/**
+ * External dependencies
+ */
+import { css, cx } from 'emotion';
+
+/**
+ * Internal dependencies
+ */
+import { useContextSystem } from '../ui/context';
+// eslint-disable-next-line no-duplicate-imports
+import type { ViewOwnProps } from '../ui/context';
+import { space } from '../ui/utils/space';
+
+const isDefined = < T >( o: T ): o is Exclude< T, null | undefined > =>
+ typeof o !== 'undefined' && o !== null;
+
+export interface SpacerProps {
+ /**
+ * Adjusts all margins.
+ */
+ margin?: number;
+ /**
+ * Adjusts top and bottom margins.
+ */
+ marginY?: number;
+ /**
+ * Adjusts left and right margins.
+ */
+ marginX?: number;
+ /**
+ * Adjusts top margins.
+ */
+ marginTop?: number;
+ /**
+ * Adjusts bottom margins.
+ *
+ * @default 2
+ */
+ marginBottom?: number;
+ /**
+ * Adjusts left margins.
+ */
+ marginLeft?: number;
+ /**
+ * Adjusts right margins.
+ */
+ marginRight?: number;
+ /**
+ * Adjusts all padding.
+ */
+ padding?: number;
+ /**
+ * Adjusts top and bottom padding.
+ */
+ paddingY?: number;
+ /**
+ * Adjusts left and right padding.
+ */
+ paddingX?: number;
+ /**
+ * Adjusts top padding.
+ */
+ paddingTop?: number;
+ /**
+ * Adjusts bottom padding.
+ */
+ paddingBottom?: number;
+ /**
+ * Adjusts left padding.
+ */
+ paddingLeft?: number;
+ /**
+ * Adjusts right padding.
+ */
+ paddingRight?: number;
+}
+
+export function useSpacer( props: ViewOwnProps< SpacerProps, 'div' > ) {
+ const {
+ className,
+ margin,
+ marginBottom = 2,
+ marginLeft,
+ marginRight,
+ marginTop,
+ marginX,
+ marginY,
+ padding,
+ paddingBottom,
+ paddingLeft,
+ paddingRight,
+ paddingTop,
+ paddingX,
+ paddingY,
+ ...otherProps
+ } = useContextSystem( props, 'Spacer' );
+
+ const classes = cx(
+ isDefined( marginTop ) &&
+ css`
+ margin-top: ${ space( marginTop ) };
+ `,
+ isDefined( marginBottom ) &&
+ css`
+ margin-bottom: ${ space( marginBottom ) };
+ `,
+ isDefined( marginLeft ) &&
+ css`
+ margin-left: ${ space( marginLeft ) };
+ `,
+ isDefined( marginRight ) &&
+ css`
+ margin-right: ${ space( marginRight ) };
+ `,
+ isDefined( marginX ) &&
+ css`
+ margin-left: ${ space( marginX ) };
+ margin-right: ${ space( marginX ) };
+ `,
+ isDefined( marginY ) &&
+ css`
+ margin-bottom: ${ space( marginY ) };
+ margin-top: ${ space( marginY ) };
+ `,
+ isDefined( margin ) &&
+ css`
+ margin: ${ space( margin ) };
+ `,
+ isDefined( paddingTop ) &&
+ css`
+ padding-top: ${ space( paddingTop ) };
+ `,
+ isDefined( paddingBottom ) &&
+ css`
+ padding-bottom: ${ space( paddingBottom ) };
+ `,
+ isDefined( paddingLeft ) &&
+ css`
+ padding-left: ${ space( paddingLeft ) };
+ `,
+ isDefined( paddingRight ) &&
+ css`
+ padding-right: ${ space( paddingRight ) };
+ `,
+ isDefined( paddingX ) &&
+ css`
+ padding-left: ${ space( paddingX ) };
+ padding-right: ${ space( paddingX ) };
+ `,
+ isDefined( paddingY ) &&
+ css`
+ padding-bottom: ${ space( paddingY ) };
+ padding-top: ${ space( paddingY ) };
+ `,
+ isDefined( padding ) &&
+ css`
+ padding: ${ space( padding ) };
+ `,
+ className
+ );
+
+ return { ...otherProps, className: classes };
+}
diff --git a/packages/components/src/spacer/index.ts b/packages/components/src/spacer/index.ts
new file mode 100644
index 00000000000000..765121f409b066
--- /dev/null
+++ b/packages/components/src/spacer/index.ts
@@ -0,0 +1,3 @@
+export { default as Spacer } from './component';
+export { useSpacer } from './hook';
+export type { SpacerProps } from './hook';
diff --git a/packages/components/src/spacer/test/__snapshots__/index.js.snap b/packages/components/src/spacer/test/__snapshots__/index.js.snap
new file mode 100644
index 00000000000000..ecf40a7e47be27
--- /dev/null
+++ b/packages/components/src/spacer/test/__snapshots__/index.js.snap
@@ -0,0 +1,199 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`props should render correctly 1`] = `
+.emotion-0 {
+ margin-bottom: calc(4px * 2);
+}
+
+
+`;
+
+exports[`props should render margin 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+- "margin": "calc(4px * 5)",
+ "margin-bottom": "calc(4px * 2)",
+ },
+ ]
+`;
+
+exports[`props should render marginBottom 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+- "margin-bottom": "calc(4px * 5)",
++ "margin-bottom": "calc(4px * 2)",
+ },
+ ]
+`;
+
+exports[`props should render marginLeft 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+ "margin-bottom": "calc(4px * 2)",
+- "margin-left": "calc(4px * 5)",
+ },
+ ]
+`;
+
+exports[`props should render marginRight 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+ "margin-bottom": "calc(4px * 2)",
+- "margin-right": "calc(4px * 5)",
+ },
+ ]
+`;
+
+exports[`props should render marginTop 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+ "margin-bottom": "calc(4px * 2)",
+- "margin-top": "calc(4px * 5)",
+ },
+ ]
+`;
+
+exports[`props should render marginX 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+ "margin-bottom": "calc(4px * 2)",
+- "margin-left": "calc(4px * 5)",
+- "margin-right": "calc(4px * 5)",
+ },
+ ]
+`;
+
+exports[`props should render marginY 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+- "margin-bottom": "calc(4px * 5)",
+- "margin-top": "calc(4px * 5)",
++ "margin-bottom": "calc(4px * 2)",
+ },
+ ]
+`;
+
+exports[`props should render padding 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+ "margin-bottom": "calc(4px * 2)",
+- "padding": "calc(4px * 5)",
+ },
+ ]
+`;
+
+exports[`props should render paddingBottom 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+ "margin-bottom": "calc(4px * 2)",
+- "padding-bottom": "calc(4px * 5)",
+ },
+ ]
+`;
+
+exports[`props should render paddingLeft 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+ "margin-bottom": "calc(4px * 2)",
+- "padding-left": "calc(4px * 5)",
+ },
+ ]
+`;
+
+exports[`props should render paddingRight 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+ "margin-bottom": "calc(4px * 2)",
+- "padding-right": "calc(4px * 5)",
+ },
+ ]
+`;
+
+exports[`props should render paddingTop 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+ "margin-bottom": "calc(4px * 2)",
+- "padding-top": "calc(4px * 5)",
+ },
+ ]
+`;
+
+exports[`props should render paddingX 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+ "margin-bottom": "calc(4px * 2)",
+- "padding-left": "calc(4px * 5)",
+- "padding-right": "calc(4px * 5)",
+ },
+ ]
+`;
+
+exports[`props should render paddingY 1`] = `
+Snapshot Diff:
+- Received styles
++ Base styles
+
+ Array [
+ Object {
+ "margin-bottom": "calc(4px * 2)",
+- "padding-bottom": "calc(4px * 5)",
+- "padding-top": "calc(4px * 5)",
+ },
+ ]
+`;
diff --git a/packages/components/src/spacer/test/index.js b/packages/components/src/spacer/test/index.js
new file mode 100644
index 00000000000000..47d9ab1833596c
--- /dev/null
+++ b/packages/components/src/spacer/test/index.js
@@ -0,0 +1,118 @@
+/**
+ * External dependencies
+ */
+import { render } from '@testing-library/react';
+
+/**
+ * Internal dependencies
+ */
+import { Spacer } from '../index';
+
+describe( 'props', () => {
+ let base;
+ beforeEach( () => {
+ base = render( ).container;
+ } );
+
+ test( 'should render correctly', () => {
+ expect( base.firstChild ).toMatchSnapshot();
+ } );
+
+ test( 'should render margin', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+
+ test( 'should render marginX', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+
+ test( 'should render marginY', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+
+ test( 'should render marginTop', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+
+ test( 'should render marginBottom', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+
+ test( 'should render marginLeft', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+
+ test( 'should render marginRight', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+
+ test( 'should render padding', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+
+ test( 'should render paddingX', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+
+ test( 'should render paddingY', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+
+ test( 'should render paddingTop', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+
+ test( 'should render paddingBottom', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+
+ test( 'should render paddingLeft', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+
+ test( 'should render paddingRight', () => {
+ const { container } = render( );
+ expect( container.firstChild ).toMatchStyleDiffSnapshot(
+ base.firstChild
+ );
+ } );
+} );
diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json
index f4c7d9364e0b6f..c93cc82efe1bb6 100644
--- a/packages/components/tsconfig.json
+++ b/packages/components/tsconfig.json
@@ -25,6 +25,7 @@
"src/scroll-lock/**/*",
"src/shortcut/**/*",
"src/spinner/**/*",
+ "src/spacer/**/*",
"src/tip/**/*",
"src/truncate/**/*",
"src/ui/**/*",