diff --git a/docs/manifest.json b/docs/manifest.json
index dca4d7d955b5b2..a06a68b96f93a5 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -737,6 +737,42 @@
"markdown_source": "../packages/components/src/confirm-dialog/README.md",
"parent": "components"
},
+ {
+ "title": "CustomSelectControlArrow",
+ "slug": "custom-select-control-arrow",
+ "markdown_source": "../packages/components/src/custom-select-control-new/custom-select-control-arrow/README.md",
+ "parent": "components"
+ },
+ {
+ "title": "CustomSelectControlGroupLabel",
+ "slug": "custom-select-control-group-label",
+ "markdown_source": "../packages/components/src/custom-select-control-new/custom-select-control-group-label/README.md",
+ "parent": "components"
+ },
+ {
+ "title": "CustomSelectControlGroup",
+ "slug": "custom-select-control-group",
+ "markdown_source": "../packages/components/src/custom-select-control-new/custom-select-control-group/README.md",
+ "parent": "components"
+ },
+ {
+ "title": "CustomSelectControlItem",
+ "slug": "custom-select-control-item",
+ "markdown_source": "../packages/components/src/custom-select-control-new/custom-select-control-item/README.md",
+ "parent": "components"
+ },
+ {
+ "title": "CustomSelectControlSeparator",
+ "slug": "custom-select-control-separator",
+ "markdown_source": "../packages/components/src/custom-select-control-new/custom-select-control-separator/README.md",
+ "parent": "components"
+ },
+ {
+ "title": "CustomSelectControl",
+ "slug": "custom-select-control",
+ "markdown_source": "../packages/components/src/custom-select-control-new/custom-select-control/README.md",
+ "parent": "components"
+ },
{
"title": "CustomSelectControl",
"slug": "custom-select-control",
diff --git a/package-lock.json b/package-lock.json
index afbbfeaa5f8e66..29a2076b26ecfb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16699,6 +16699,7 @@
"@wordpress/primitives": "file:packages/primitives",
"@wordpress/rich-text": "file:packages/rich-text",
"@wordpress/warning": "file:packages/warning",
+ "ariakit": "2.0.0-next.36",
"classnames": "^2.3.1",
"colord": "^2.7.0",
"dom-scroll-into-view": "^1.2.1",
@@ -16729,6 +16730,33 @@
"csstype": "^3.0.2"
}
},
+ "@floating-ui/core": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.0.0.tgz",
+ "integrity": "sha512-sm3nW0hHAxTv3gRDdCH8rNVQxijF+qPFo5gAeXCErRjKC7Qc28lIQ3R9Vd7Gw+KgwfA7RhRydDFuGeI0peGq7A=="
+ },
+ "@floating-ui/dom": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.0.0.tgz",
+ "integrity": "sha512-PMqJvY5Fae8HVQgUqM+lidprS6p9LSvB0AUhCdYKqr3YCaV+WaWCeVNBtXPRY2YIdrgcsL2+vd5F07FxgihHUw==",
+ "requires": {
+ "@floating-ui/core": "^1.0.0"
+ }
+ },
+ "ariakit": {
+ "version": "2.0.0-next.36",
+ "resolved": "https://registry.npmjs.org/ariakit/-/ariakit-2.0.0-next.36.tgz",
+ "integrity": "sha512-H/ZqRgy5tGGKOcOsZ24lc5cBoQ83vgCFd+mC87UWdIEdYqhKNPPjFZona/V/l0SRtn9Mar+t93QbggyThOw6Qg==",
+ "requires": {
+ "@floating-ui/dom": "^1.0.0",
+ "ariakit-utils": "0.17.0-next.23"
+ }
+ },
+ "ariakit-utils": {
+ "version": "0.17.0-next.23",
+ "resolved": "https://registry.npmjs.org/ariakit-utils/-/ariakit-utils-0.17.0-next.23.tgz",
+ "integrity": "sha512-r6a8rvjTBNbdNVWhGm4XL8hTlpIlP0G+yJf3No48kK6QpR1JN9QinLI/wMwWwJnxDYfwnBhkbcROCqF7iT/4ig=="
+ },
"colord": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/colord/-/colord-2.8.0.tgz",
diff --git a/packages/components/package.json b/packages/components/package.json
index e678c3a5514939..68115fe29233c0 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -53,6 +53,7 @@
"@wordpress/primitives": "file:../primitives",
"@wordpress/rich-text": "file:../rich-text",
"@wordpress/warning": "file:../warning",
+ "ariakit": "2.0.0-next.36",
"classnames": "^2.3.1",
"colord": "^2.7.0",
"dom-scroll-into-view": "^1.2.1",
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-arrow/README.md b/packages/components/src/custom-select-control-new/custom-select-control-arrow/README.md
new file mode 100644
index 00000000000000..4a7e8ffaede437
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-arrow/README.md
@@ -0,0 +1,3 @@
+# CustomSelectControlArrow
+
+TODO
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-arrow/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-arrow/component.tsx
new file mode 100644
index 00000000000000..57dac95f1e1bb9
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-arrow/component.tsx
@@ -0,0 +1,32 @@
+/**
+ * External dependencies
+ */
+import type { ForwardedRef } from 'react';
+import { SelectArrow } from 'ariakit/select';
+
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { useSelectControlArrow } from './hook';
+import type { WordPressComponentProps } from '../../ui/context';
+import type { SelectControlArrowProps } from '../types';
+
+const UnforwardedSelectControlArrow = (
+ props: WordPressComponentProps< SelectControlArrowProps, 'span', false >,
+ forwardedRef: ForwardedRef< any >
+) => {
+ const allProps = useSelectControlArrow( props );
+
+ // TODO: investigate incompatibility with the "as" prop.
+ return ;
+};
+
+// TODO: JSDocs
+export const SelectControlArrow = forwardRef( UnforwardedSelectControlArrow );
+
+export default SelectControlArrow;
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-arrow/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-arrow/hook.ts
new file mode 100644
index 00000000000000..436f30d03cc6bd
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-arrow/hook.ts
@@ -0,0 +1,30 @@
+/**
+ * WordPress dependencies
+ */
+import { useMemo } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import * as styles from '../styles';
+import type { WordPressComponentProps } from '../../ui/context';
+import { useCx } from '../../utils/hooks/use-cx';
+import type { SelectControlArrowProps } from '../types';
+
+// TODO:
+// - should we allow polymorphism ?
+export const useSelectControlArrow = ( {
+ className,
+ ...props
+}: WordPressComponentProps< SelectControlArrowProps, 'span', false > ) => {
+ const cx = useCx();
+ const arrowClassName = useMemo(
+ () => cx( styles.arrow, className ),
+ [ className, cx ]
+ );
+
+ return {
+ ...props,
+ className: arrowClassName,
+ };
+};
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-arrow/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-arrow/index.ts
new file mode 100644
index 00000000000000..e53f4ca8179fbb
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-arrow/index.ts
@@ -0,0 +1,2 @@
+export { default } from './component';
+export { useSelectControlArrow } from './hook';
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group-label/README.md b/packages/components/src/custom-select-control-new/custom-select-control-group-label/README.md
new file mode 100644
index 00000000000000..4a019aac2f804f
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-group-label/README.md
@@ -0,0 +1,3 @@
+# CustomSelectControlGroupLabel
+
+TODO
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group-label/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-group-label/component.tsx
new file mode 100644
index 00000000000000..f8b7e20bb9231a
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-group-label/component.tsx
@@ -0,0 +1,38 @@
+/**
+ * External dependencies
+ */
+import type { ForwardedRef } from 'react';
+import { SelectGroupLabel } from 'ariakit/select';
+
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { useSelectControlGroupLabel } from './hook';
+import type { WordPressComponentProps } from '../../ui/context';
+import type { SelectControlGroupLabelProps } from '../types';
+
+const UnforwardedSelectControlGroupLabel = (
+ props: WordPressComponentProps<
+ SelectControlGroupLabelProps,
+ 'div',
+ false
+ >,
+ forwardedRef: ForwardedRef< any >
+) => {
+ const allProps = useSelectControlGroupLabel( props );
+
+ // TODO: investigate incompatibility with the "as" prop.
+ return ;
+};
+
+// TODO: JSDocs
+export const SelectControlGroupLabel = forwardRef(
+ UnforwardedSelectControlGroupLabel
+);
+
+export default SelectControlGroupLabel;
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group-label/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-group-label/hook.ts
new file mode 100644
index 00000000000000..9bdb8bb675d579
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-group-label/hook.ts
@@ -0,0 +1,31 @@
+/**
+ * WordPress dependencies
+ */
+import { useMemo } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import * as styles from '../styles';
+import type { WordPressComponentProps } from '../../ui/context';
+import { useCx } from '../../utils/hooks/use-cx';
+import type { SelectControlGroupLabelProps } from '../types';
+
+// TODO:
+// - should we use 'option' instead of `div` for props inheritance?
+// - should we allow polymorphism ?
+export const useSelectControlGroupLabel = ( {
+ className,
+ ...props
+}: WordPressComponentProps< SelectControlGroupLabelProps, 'div', false > ) => {
+ const cx = useCx();
+ const groupLabelClassName = useMemo(
+ () => cx( styles.groupLabel, className ),
+ [ className, cx ]
+ );
+
+ return {
+ ...props,
+ className: groupLabelClassName,
+ };
+};
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group-label/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-group-label/index.ts
new file mode 100644
index 00000000000000..1ac138c881dca4
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-group-label/index.ts
@@ -0,0 +1,2 @@
+export { default } from './component';
+export { useSelectControlGroupLabel } from './hook';
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group/README.md b/packages/components/src/custom-select-control-new/custom-select-control-group/README.md
new file mode 100644
index 00000000000000..fd210f9f95ce72
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-group/README.md
@@ -0,0 +1,3 @@
+# CustomSelectControlGroup
+
+TODO
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-group/component.tsx
new file mode 100644
index 00000000000000..b62994e3e4c709
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-group/component.tsx
@@ -0,0 +1,32 @@
+/**
+ * External dependencies
+ */
+import type { ForwardedRef } from 'react';
+import { SelectGroup } from 'ariakit/select';
+
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { useSelectControlGroup } from './hook';
+import type { WordPressComponentProps } from '../../ui/context';
+import type { SelectControlGroupProps } from '../types';
+
+const UnforwardedSelectControlGroup = (
+ props: WordPressComponentProps< SelectControlGroupProps, 'div', false >,
+ forwardedRef: ForwardedRef< any >
+) => {
+ const allProps = useSelectControlGroup( props );
+
+ // TODO: investigate incompatibility with the "as" prop.
+ return ;
+};
+
+// TODO: JSDocs
+export const SelectControlGroup = forwardRef( UnforwardedSelectControlGroup );
+
+export default SelectControlGroup;
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-group/hook.ts
new file mode 100644
index 00000000000000..4f406cfee2363e
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-group/hook.ts
@@ -0,0 +1,30 @@
+/**
+ * WordPress dependencies
+ */
+import { useMemo } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import * as styles from '../styles';
+import type { WordPressComponentProps } from '../../ui/context';
+import { useCx } from '../../utils/hooks/use-cx';
+import type { SelectControlGroupProps } from '../types';
+
+// TODO:
+// - should we allow polymorphism ?
+export const useSelectControlGroup = ( {
+ className,
+ ...props
+}: WordPressComponentProps< SelectControlGroupProps, 'div', false > ) => {
+ const cx = useCx();
+ const groupClassName = useMemo(
+ () => cx( styles.group, className ),
+ [ className, cx ]
+ );
+
+ return {
+ ...props,
+ className: groupClassName,
+ };
+};
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-group/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-group/index.ts
new file mode 100644
index 00000000000000..94cd33031a1e9f
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-group/index.ts
@@ -0,0 +1,2 @@
+export { default } from './component';
+export { useSelectControlGroup } from './hook';
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item-check/README.md b/packages/components/src/custom-select-control-new/custom-select-control-item-check/README.md
new file mode 100644
index 00000000000000..c24ffb32df44f2
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-item-check/README.md
@@ -0,0 +1,3 @@
+# CustomSelectControlItemCheck
+
+TODO
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item-check/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-item-check/component.tsx
new file mode 100644
index 00000000000000..55c3c94efad95c
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-item-check/component.tsx
@@ -0,0 +1,37 @@
+/**
+ * External dependencies
+ */
+import type { ForwardedRef } from 'react';
+import { SelectItemCheck } from 'ariakit/select';
+
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { useSelectControlItemCheck } from './hook';
+import type { WordPressComponentProps } from '../../ui/context';
+import type { SelectControlItemCheckProps } from '../types';
+
+const UnforwardedSelectControlItemCheck = (
+ props: WordPressComponentProps<
+ SelectControlItemCheckProps,
+ 'span',
+ false
+ >,
+ forwardedRef: ForwardedRef< any >
+) => {
+ const allProps = useSelectControlItemCheck( props );
+
+ return ;
+};
+
+// TODO: JSDocs
+export const SelectControlItemCheck = forwardRef(
+ UnforwardedSelectControlItemCheck
+);
+
+export default SelectControlItemCheck;
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item-check/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-item-check/hook.ts
new file mode 100644
index 00000000000000..5bbbba0aa2883d
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-item-check/hook.ts
@@ -0,0 +1,28 @@
+/**
+ * WordPress dependencies
+ */
+import { useMemo } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import * as styles from '../styles';
+import type { WordPressComponentProps } from '../../ui/context';
+import { useCx } from '../../utils/hooks/use-cx';
+import type { SelectControlItemCheckProps } from '../types';
+
+export const useSelectControlItemCheck = ( {
+ className,
+ ...props
+}: WordPressComponentProps< SelectControlItemCheckProps, 'span', false > ) => {
+ const cx = useCx();
+ const itemCheckClassName = useMemo(
+ () => cx( styles.itemCheck, className ),
+ [ className, cx ]
+ );
+
+ return {
+ ...props,
+ className: itemCheckClassName,
+ };
+};
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item-check/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-item-check/index.ts
new file mode 100644
index 00000000000000..fd20b7feb64fb7
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-item-check/index.ts
@@ -0,0 +1,2 @@
+export { default } from './component';
+export { useSelectControlItemCheck } from './hook';
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item/README.md b/packages/components/src/custom-select-control-new/custom-select-control-item/README.md
new file mode 100644
index 00000000000000..f1b9345231bb11
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-item/README.md
@@ -0,0 +1,3 @@
+# CustomSelectControlItem
+
+TODO
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-item/component.tsx
new file mode 100644
index 00000000000000..432ce9703c9888
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-item/component.tsx
@@ -0,0 +1,32 @@
+/**
+ * External dependencies
+ */
+import type { ForwardedRef } from 'react';
+import { SelectItem } from 'ariakit/select';
+
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { useSelectControlItem } from './hook';
+import type { WordPressComponentProps } from '../../ui/context';
+import type { SelectControlItemProps } from '../types';
+
+const UnforwardedSelectControlItem = (
+ props: WordPressComponentProps< SelectControlItemProps, 'div', false >,
+ forwardedRef: ForwardedRef< any >
+) => {
+ const allProps = useSelectControlItem( props );
+
+ // TODO: investigate incompatibility with the "as" prop.
+ return ;
+};
+
+// TODO: JSDocs
+export const SelectControlItem = forwardRef( UnforwardedSelectControlItem );
+
+export default SelectControlItem;
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-item/hook.ts
new file mode 100644
index 00000000000000..fe64d2f85ce571
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-item/hook.ts
@@ -0,0 +1,31 @@
+/**
+ * WordPress dependencies
+ */
+import { useMemo } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import * as styles from '../styles';
+import type { WordPressComponentProps } from '../../ui/context';
+import { useCx } from '../../utils/hooks/use-cx';
+import type { SelectControlItemProps } from '../types';
+
+// TODO:
+// - should we use 'option' instead of `div` for props inheritance?
+// - should we allow polymorphism ?
+export const useSelectControlItem = ( {
+ className,
+ ...props
+}: WordPressComponentProps< SelectControlItemProps, 'div', false > ) => {
+ const cx = useCx();
+ const itemClassName = useMemo(
+ () => cx( styles.item, className ),
+ [ className, cx ]
+ );
+
+ return {
+ ...props,
+ className: itemClassName,
+ };
+};
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-item/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-item/index.ts
new file mode 100644
index 00000000000000..42c91c3acd1c07
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-item/index.ts
@@ -0,0 +1,2 @@
+export { default } from './component';
+export { useSelectControlItem } from './hook';
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-row/README.md b/packages/components/src/custom-select-control-new/custom-select-control-row/README.md
new file mode 100644
index 00000000000000..4d70ab39fa0e60
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-row/README.md
@@ -0,0 +1,3 @@
+# CustomSelectControlRow
+
+TODO
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-row/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-row/component.tsx
new file mode 100644
index 00000000000000..d4c2081ca3680c
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-row/component.tsx
@@ -0,0 +1,31 @@
+/**
+ * External dependencies
+ */
+import type { ForwardedRef } from 'react';
+import { SelectRow } from 'ariakit/select';
+
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { useSelectControlRow } from './hook';
+import type { WordPressComponentProps } from '../../ui/context';
+import type { SelectControlRowProps } from '../types';
+
+const UnforwardedSelectControlRow = (
+ props: WordPressComponentProps< SelectControlRowProps, 'div', false >,
+ forwardedRef: ForwardedRef< any >
+) => {
+ const allProps = useSelectControlRow( props );
+
+ return ;
+};
+
+// TODO: JSDocs
+export const SelectControlRow = forwardRef( UnforwardedSelectControlRow );
+
+export default SelectControlRow;
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-row/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-row/hook.ts
new file mode 100644
index 00000000000000..9dbcec2b562476
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-row/hook.ts
@@ -0,0 +1,28 @@
+/**
+ * WordPress dependencies
+ */
+import { useMemo } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import * as styles from '../styles';
+import type { WordPressComponentProps } from '../../ui/context';
+import { useCx } from '../../utils/hooks/use-cx';
+import type { SelectControlRowProps } from '../types';
+
+export const useSelectControlRow = ( {
+ className,
+ ...props
+}: WordPressComponentProps< SelectControlRowProps, 'div', false > ) => {
+ const cx = useCx();
+ const rowClassName = useMemo(
+ () => cx( styles.row, className ),
+ [ className, cx ]
+ );
+
+ return {
+ ...props,
+ className: rowClassName,
+ };
+};
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-row/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-row/index.ts
new file mode 100644
index 00000000000000..9171498f9b008d
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-row/index.ts
@@ -0,0 +1,2 @@
+export { default } from './component';
+export { useSelectControlRow } from './hook';
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-separator/README.md b/packages/components/src/custom-select-control-new/custom-select-control-separator/README.md
new file mode 100644
index 00000000000000..e3ca90cc5480bb
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-separator/README.md
@@ -0,0 +1,3 @@
+# CustomSelectControlSeparator
+
+TODO
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-separator/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control-separator/component.tsx
new file mode 100644
index 00000000000000..179d10dbac6452
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-separator/component.tsx
@@ -0,0 +1,33 @@
+/**
+ * External dependencies
+ */
+import type { ForwardedRef } from 'react';
+import { SelectSeparator } from 'ariakit/select';
+
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { useSelectControlSeparator } from './hook';
+import type { WordPressComponentProps } from '../../ui/context';
+import type { SelectControlSeparatorProps } from '../types';
+
+const UnforwardedSelectControlSeparator = (
+ props: WordPressComponentProps< SelectControlSeparatorProps, 'hr', false >,
+ forwardedRef: ForwardedRef< any >
+) => {
+ const allProps = useSelectControlSeparator( props );
+
+ return ;
+};
+
+// TODO: JSDocs
+export const SelectControlSeparator = forwardRef(
+ UnforwardedSelectControlSeparator
+);
+
+export default SelectControlSeparator;
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-separator/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control-separator/hook.ts
new file mode 100644
index 00000000000000..49220721da7545
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-separator/hook.ts
@@ -0,0 +1,28 @@
+/**
+ * WordPress dependencies
+ */
+import { useMemo } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import * as styles from '../styles';
+import type { WordPressComponentProps } from '../../ui/context';
+import { useCx } from '../../utils/hooks/use-cx';
+import type { SelectControlSeparatorProps } from '../types';
+
+export const useSelectControlSeparator = ( {
+ className,
+ ...props
+}: WordPressComponentProps< SelectControlSeparatorProps, 'hr', false > ) => {
+ const cx = useCx();
+ const separatorClassName = useMemo(
+ () => cx( styles.separator, className ),
+ [ className, cx ]
+ );
+
+ return {
+ ...props,
+ className: separatorClassName,
+ };
+};
diff --git a/packages/components/src/custom-select-control-new/custom-select-control-separator/index.ts b/packages/components/src/custom-select-control-new/custom-select-control-separator/index.ts
new file mode 100644
index 00000000000000..f4610cf6cb8d68
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control-separator/index.ts
@@ -0,0 +1,2 @@
+export { default } from './component';
+export { useSelectControlSeparator } from './hook';
diff --git a/packages/components/src/custom-select-control-new/custom-select-control/README.md b/packages/components/src/custom-select-control-new/custom-select-control/README.md
new file mode 100644
index 00000000000000..54862525d4f901
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control/README.md
@@ -0,0 +1,3 @@
+# CustomSelectControl
+
+TODO
diff --git a/packages/components/src/custom-select-control-new/custom-select-control/component.tsx b/packages/components/src/custom-select-control-new/custom-select-control/component.tsx
new file mode 100644
index 00000000000000..ae336845d7fcc1
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control/component.tsx
@@ -0,0 +1,101 @@
+/**
+ * External dependencies
+ */
+import type { ForwardedRef } from 'react';
+import { Select, SelectLabel, SelectPopover } from 'ariakit/select';
+
+/**
+ * WordPress dependencies
+ */
+import { forwardRef } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { useSelectControl } from './hook';
+import type { WordPressComponentProps } from '../../ui/context';
+import { CustomSelectControlItem, CustomSelectControlArrow } from '../';
+import type { SelectControlOption, SelectControlProps } from '../types';
+
+const CustomDisplayedValue = ( {
+ options,
+ value,
+}: {
+ options: SelectControlOption[];
+ value?: string;
+} ) => (
+ <>
+ { /* Use the label associated to the option's value, fallback to the value itself */ }
+ { options.find( ( option ) => option.value === value )?.label ?? value }
+
+ >
+);
+
+const UnforwardedSelectControl = (
+ props: WordPressComponentProps< SelectControlProps, 'select', false >,
+ forwardedRef: ForwardedRef< any >
+) => {
+ const {
+ label,
+ options,
+ children,
+ selectState,
+ wrapperClassName,
+ labelClassName,
+ selectClassName,
+ popoverClassName,
+ } = useSelectControl( props );
+
+ return (
+
+
+ { label }
+
+
+
+ { /* Popover arrow? */ }
+ { children ??
+ options?.map( ( option, index ) => {
+ const key =
+ option.id ||
+ `${ option.label }-${ option.value }-${ index }`;
+ return (
+
+ { option.label }
+
+ );
+ } ) }
+
+
+ );
+};
+
+// TODO: JSDocs
+export const SelectControl = forwardRef( UnforwardedSelectControl );
+
+export default SelectControl;
diff --git a/packages/components/src/custom-select-control-new/custom-select-control/hook.ts b/packages/components/src/custom-select-control-new/custom-select-control/hook.ts
new file mode 100644
index 00000000000000..8a195051c4db8f
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control/hook.ts
@@ -0,0 +1,64 @@
+/**
+ * External dependencies
+ */
+import { useSelectState } from 'ariakit/select';
+
+/**
+ * WordPress dependencies
+ */
+import { useMemo } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import * as styles from '../styles';
+import type { WordPressComponentProps } from '../../ui/context';
+import { useCx } from '../../utils/hooks/use-cx';
+import type { SelectControlProps } from '../types';
+
+// TODO:
+// - should we use 'select' instead of `div` for props inheritance?
+// - should we allow polymorphism ?
+export const useSelectControl = ( {
+ value,
+ className,
+ ...props
+}: WordPressComponentProps< SelectControlProps, 'select', false > ) => {
+ // TODO: take some of these settings as props?
+ const selectState = useSelectState( {
+ // TODO: check if it works, understand if we should expose
+ // a different prop for the initial value?
+ defaultValue: value,
+ sameWidth: true,
+ gutter: 4,
+ } );
+
+ // TODO: deprecate options prop
+
+ const cx = useCx();
+ const wrapperClassName = useMemo(
+ () => cx( styles.wrapper, className ),
+ [ className, cx ]
+ );
+ const labelClassName = useMemo(
+ () => cx( styles.label, className ),
+ [ className, cx ]
+ );
+ const selectClassName = useMemo(
+ () => cx( styles.select, className ),
+ [ className, cx ]
+ );
+ const popoverClassName = useMemo(
+ () => cx( styles.popover, className ),
+ [ className, cx ]
+ );
+
+ return {
+ ...props,
+ selectState,
+ wrapperClassName,
+ labelClassName,
+ selectClassName,
+ popoverClassName,
+ };
+};
diff --git a/packages/components/src/custom-select-control-new/custom-select-control/index.ts b/packages/components/src/custom-select-control-new/custom-select-control/index.ts
new file mode 100644
index 00000000000000..b4aa7c202c1766
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/custom-select-control/index.ts
@@ -0,0 +1,2 @@
+export { default } from './component';
+export { useSelectControl } from './hook';
diff --git a/packages/components/src/custom-select-control-new/index.ts b/packages/components/src/custom-select-control-new/index.ts
new file mode 100644
index 00000000000000..9991a9c298f82e
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/index.ts
@@ -0,0 +1,8 @@
+export { default as CustomSelectControl } from './custom-select-control';
+export { default as CustomSelectControlItem } from './custom-select-control-item';
+export { default as CustomSelectControlGroup } from './custom-select-control-group';
+export { default as CustomSelectControlGroupLabel } from './custom-select-control-group-label';
+export { default as CustomSelectControlArrow } from './custom-select-control-arrow';
+export { default as CustomSelectControlSeparator } from './custom-select-control-separator';
+export { default as CustomSelectControlRow } from './custom-select-control-row';
+export { default as CustomSelectControlItemCheck } from './custom-select-control-item-check';
diff --git a/packages/components/src/custom-select-control-new/stories/index.tsx b/packages/components/src/custom-select-control-new/stories/index.tsx
new file mode 100644
index 00000000000000..3f3b803e4b7af5
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/stories/index.tsx
@@ -0,0 +1,106 @@
+/**
+ * External dependencies
+ */
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+
+/**
+ * WordPress dependencies
+ */
+// import { useState } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import {
+ CustomSelectControl,
+ CustomSelectControlItem,
+ CustomSelectControlGroup,
+ CustomSelectControlGroupLabel,
+ CustomSelectControlSeparator,
+} from '../';
+
+const meta: ComponentMeta< typeof CustomSelectControl > = {
+ component: CustomSelectControl,
+ title: 'Components (Experimental)/CustomSelectControlNew',
+ argTypes: {},
+ parameters: {
+ controls: {
+ expanded: true,
+ },
+ docs: { source: { state: 'open' } },
+ },
+};
+export default meta;
+
+// TODO:
+// - with `options` prop
+// - controlled vs uncontrolled
+// - with HTML `` (and related vanilla elements)?
+// - example with custom author dropdown
+
+export const Default: ComponentStory< typeof CustomSelectControl > = (
+ args
+) => (
+
+
+
+ { /* Custom item label */ }
+ Planet Earth
+
+
+
+
+
+);
+Default.args = {
+ label: 'Choose a planet',
+};
+
+export const WithGroupsAndSeparators: ComponentStory<
+ typeof CustomSelectControl
+> = ( args ) => {
+ return (
+
+ { [
+ { label: 'Primary', values: [ 'Red', 'Yellow', 'Blue' ] },
+ { label: 'Secondary', values: [ 'Orange', 'Green', 'Purple' ] },
+ {
+ label: 'Tertiary',
+ values: [
+ 'Amber',
+ 'Vermilion',
+ 'Magenta',
+ 'Violet',
+ 'Teal',
+ 'Chartreuse',
+ ],
+ },
+ ].map( ( { label, values }, groupIndex, groupArray ) => (
+ <>
+
+
+ { label }
+
+ { values.map( ( value, valueIndex ) => (
+
+ ) ) }
+
+ { groupIndex < groupArray.length - 1 ? (
+
+ ) : null }
+ >
+ ) ) }
+
+ );
+};
+WithGroupsAndSeparators.args = {
+ label: 'Pick a color',
+};
diff --git a/packages/components/src/custom-select-control-new/styles.ts b/packages/components/src/custom-select-control-new/styles.ts
new file mode 100644
index 00000000000000..b90ba6fa08dca5
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/styles.ts
@@ -0,0 +1,130 @@
+/**
+ * External dependencies
+ */
+import { css } from '@emotion/react';
+
+/**
+ * Internal dependencies
+ */
+
+const focused = css`
+ outline: 2px solid hsl( 204, 100%, 40% );
+`;
+
+// TODO: convert to Flex or HStack
+export const wrapper = css`
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+`;
+
+// TODO: use base control label? Text? Heading?
+export const label = css``;
+
+// TODO: convert to Flex?
+export const select = css`
+ /* Should be set by parent */
+ width: 200px;
+ display: flex;
+ height: 2.5rem;
+ cursor: default;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.25rem;
+ white-space: nowrap;
+ border-radius: 0.5rem;
+ background-color: hsl( 204, 20%, 94% );
+ padding-left: 1rem;
+ padding-right: 1rem;
+ font-size: 1rem;
+ line-height: 1.5rem;
+
+ &:hover {
+ background-color: hsl( 204, 20%, 91% );
+ }
+
+ &[aria-disabled='true'] {
+ opacity: 0.5;
+ }
+
+ &:focus-visible,
+ &[data-focus-visible] {
+ ${ focused };
+ }
+`;
+
+// TODO: convert to Flex?
+export const popover = css`
+ /* TODO: add flexibility, e.g.: min(var(--popover-available-height, 300px), 300px); */
+ max-height: 20rem;
+ z-index: 50;
+ display: flex;
+ flex-direction: column;
+ overflow: auto;
+ overscroll-behavior: contain;
+ border-radius: 0.5rem;
+ border-width: 1px;
+ border-style: solid;
+ border-color: hsl( 204, 20%, 88% );
+ background-color: hsl( 204, 20%, 100% );
+ padding: 0.5rem;
+ color: hsl( 204, 10%, 10% );
+ filter: drop-shadow( 0 4px 6px rgba( 0, 0, 0, 15% ) );
+
+ &:focus-visible,
+ &[data-focus-visible] {
+ ${ focused };
+ }
+`;
+
+export const item = css`
+ outline: none !important;
+ display: flex;
+ cursor: default;
+ scroll-margin: 0.5rem;
+ align-items: center;
+ gap: 0.5rem;
+ border-radius: 0.25rem;
+ padding: 0.5rem;
+
+ &[data-active-item] {
+ background-color: hsl( 204, 100%, 40% );
+ color: hsl( 0, 0%, 100% );
+ }
+
+ &[aria-disabled='true'] {
+ opacity: 0.5;
+ }
+`;
+
+export const itemCheck = css`
+ /* TODO: styles (decide after prepping a storybook example) */
+`;
+
+export const arrow = css``;
+
+export const group = css``;
+
+export const groupLabel = css`
+ cursor: default;
+ padding: 0.5rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+ opacity: 0.6;
+`;
+
+// TODO: convert to Flex or HStack
+export const row = css`
+ display: 'flex';
+`;
+
+export const separator = css`
+ border-color: currentcolor;
+ margin-top: 0.5rem;
+ margin-bottom: 0.5rem;
+ height: 0px;
+ border-top-width: 1px;
+ opacity: 0.25;
+ width: 100%;
+`;
diff --git a/packages/components/src/custom-select-control-new/types.ts b/packages/components/src/custom-select-control-new/types.ts
new file mode 100644
index 00000000000000..1b1efefaf061d4
--- /dev/null
+++ b/packages/components/src/custom-select-control-new/types.ts
@@ -0,0 +1,70 @@
+/**
+ * External dependencies
+ */
+import type { ReactNode } from 'react';
+
+type SelectControlOptionBase = {
+ value: string;
+ label?: string;
+ disabled?: boolean;
+};
+
+// options[] data object
+export type SelectControlOption = SelectControlOptionBase & {
+ id?: string;
+};
+
+// React component props
+export type SelectControlItemProps = SelectControlOptionBase & {
+ // Is classname necessary, with WordPressComponentProps?
+ className?: string;
+ children?: ReactNode;
+ // Do we want to expose this prop?
+ checked?: boolean;
+ // Should we expose this?
+ preventScrollOnKeyDown?: boolean;
+};
+
+export type SelectControlItemCheckProps = SelectControlOptionBase & {
+ // Is classname necessary, with WordPressComponentProps?
+ className?: string;
+ children?: ReactNode;
+};
+
+export type SelectControlProps = {
+ value?: string;
+ label?: string;
+ // TODO: explain that they are ignored if `children` is specified
+ options?: SelectControlOption[];
+ children?: ReactNode;
+ // Is classname necessary, with WordPressComponentProps?
+ className?: string;
+};
+
+export type SelectControlArrowProps = {
+ // Is classname necessary, with WordPressComponentProps?
+ className?: string;
+};
+
+export type SelectControlGroupProps = {
+ children: ReactNode;
+ // Is classname necessary, with WordPressComponentProps?
+ className?: string;
+};
+
+export type SelectControlGroupLabelProps = {
+ children: ReactNode;
+ // Is classname necessary, with WordPressComponentProps?
+ className?: string;
+};
+
+export type SelectControlSeparatorProps = {
+ // Is classname necessary, with WordPressComponentProps?
+ className?: string;
+};
+
+export type SelectControlRowProps = {
+ children: ReactNode;
+ // Is classname necessary, with WordPressComponentProps?
+ className?: string;
+};
diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json
index cc5f7e3be2634b..32167c48f423ef 100644
--- a/packages/components/tsconfig.json
+++ b/packages/components/tsconfig.json
@@ -46,6 +46,7 @@
"src/color-palette/**/*",
"src/color-picker/**/*",
"src/confirm-dialog/**/*",
+ "src/custom-select-control-new/**/*",
"src/dashicon/**/*",
"src/date-time/**/*",
"src/disabled/**/*",