diff --git a/packages/block-editor/src/components/height-control/index.js b/packages/block-editor/src/components/height-control/index.js
new file mode 100644
index 00000000000000..cc1eea0b4bad86
--- /dev/null
+++ b/packages/block-editor/src/components/height-control/index.js
@@ -0,0 +1,123 @@
+/**
+ * WordPress dependencies
+ */
+import { useMemo } from '@wordpress/element';
+import {
+ BaseControl,
+ RangeControl,
+ Flex,
+ FlexItem,
+ __experimentalSpacer as Spacer,
+ __experimentalUseCustomUnits as useCustomUnits,
+ __experimentalUnitControl as UnitControl,
+ __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue,
+} from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import useSetting from '../use-setting';
+
+const RANGE_CONTROL_CUSTOM_SETTINGS = {
+ px: { max: 1000, step: 1 },
+ '%': { max: 100, step: 1 },
+ vw: { max: 100, step: 1 },
+ vh: { max: 100, step: 1 },
+ em: { max: 50, step: 0.1 },
+ rem: { max: 50, step: 0.1 },
+};
+
+export default function HeightControl( {
+ onChange,
+ label = __( 'Height' ),
+ value,
+} ) {
+ const customRangeValue = parseFloat( value );
+
+ const units = useCustomUnits( {
+ availableUnits: useSetting( 'spacing.units' ) || [
+ '%',
+ 'px',
+ 'em',
+ 'rem',
+ 'vh',
+ 'vw',
+ ],
+ } );
+
+ const selectedUnit =
+ useMemo(
+ () => parseQuantityAndUnitFromRawValue( value ),
+ [ value ]
+ )[ 1 ] ||
+ units[ 0 ]?.value ||
+ 'px';
+
+ const handleSliderChange = ( next ) => {
+ onChange( [ next, selectedUnit ].join( '' ) );
+ };
+
+ const handleUnitChange = ( newUnit ) => {
+ // Attempt to smooth over differences between currentUnit and newUnit.
+ // This should slightly improve the experience of switching between unit types.
+ const [ currentValue, currentUnit ] =
+ parseQuantityAndUnitFromRawValue( value );
+
+ if ( [ 'em', 'rem' ].includes( newUnit ) && currentUnit === 'px' ) {
+ // Convert pixel value to an approximate of the new unit, assuming a root size of 16px.
+ onChange( ( currentValue / 16 ).toFixed( 2 ) + newUnit );
+ } else if (
+ [ 'em', 'rem' ].includes( currentUnit ) &&
+ newUnit === 'px'
+ ) {
+ // Convert to pixel value assuming a root size of 16px.
+ onChange( Math.round( currentValue * 16 ) + newUnit );
+ } else if (
+ [ 'vh', 'vw', '%' ].includes( newUnit ) &&
+ currentValue > 100
+ ) {
+ // When converting to `vh`, `vw`, or `%` units, cap the new value at 100.
+ onChange( 100 + newUnit );
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/packages/block-editor/src/components/height-control/stories/index.js b/packages/block-editor/src/components/height-control/stories/index.js
new file mode 100644
index 00000000000000..f4b586a96b0e33
--- /dev/null
+++ b/packages/block-editor/src/components/height-control/stories/index.js
@@ -0,0 +1,21 @@
+/**
+ * WordPress dependencies
+ */
+import { useState } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import HeightControl from '../';
+
+export default {
+ component: HeightControl,
+ title: 'BlockEditor/HeightControl',
+};
+
+const Template = ( props ) => {
+ const [ value, setValue ] = useState();
+ return ;
+};
+
+export const Default = Template.bind( {} );
diff --git a/packages/block-editor/src/components/height-control/style.scss b/packages/block-editor/src/components/height-control/style.scss
new file mode 100644
index 00000000000000..add0866835f767
--- /dev/null
+++ b/packages/block-editor/src/components/height-control/style.scss
@@ -0,0 +1,5 @@
+.block-editor-height-control {
+ border: 0;
+ margin: 0;
+ padding: 0;
+}
diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js
index 7743a98066c437..03ce8f3880ad32 100644
--- a/packages/block-editor/src/components/index.js
+++ b/packages/block-editor/src/components/index.js
@@ -53,6 +53,7 @@ export { default as __experimentalColorGradientControl } from './colors-gradient
export { default as __experimentalColorGradientSettingsDropdown } from './colors-gradients/dropdown';
export { default as __experimentalPanelColorGradientSettings } from './colors-gradients/panel-color-gradient-settings';
export { default as __experimentalUseMultipleOriginColorsAndGradients } from './colors-gradients/use-multiple-origin-colors-and-gradients';
+export { default as __experimentalHeightControl } from './height-control';
export {
default as __experimentalImageEditor,
ImageEditingProvider as __experimentalImageEditingProvider,
diff --git a/packages/block-editor/src/hooks/dimensions.js b/packages/block-editor/src/hooks/dimensions.js
index 44a9fd83278a95..3b33df400fcfc3 100644
--- a/packages/block-editor/src/hooks/dimensions.js
+++ b/packages/block-editor/src/hooks/dimensions.js
@@ -182,7 +182,6 @@ export function DimensionsPanel( props ) {
) }
{ ! isMinHeightDisabled && (
hasMinHeightValue( props ) }
label={ __( 'Min. height' ) }
onDeselect={ () => resetMinHeight( props ) }
diff --git a/packages/block-editor/src/hooks/min-height.js b/packages/block-editor/src/hooks/min-height.js
index 3167edba8a8293..e123f0cee98b22 100644
--- a/packages/block-editor/src/hooks/min-height.js
+++ b/packages/block-editor/src/hooks/min-height.js
@@ -2,16 +2,13 @@
* WordPress dependencies
*/
import { getBlockSupport } from '@wordpress/blocks';
-import {
- __experimentalUseCustomUnits as useCustomUnits,
- __experimentalUnitControl as UnitControl,
-} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import useSetting from '../components/use-setting';
+import HeightControl from '../components/height-control';
import { DIMENSIONS_SUPPORT_KEY } from './dimensions';
import { cleanEmptyObject } from './utils';
@@ -81,17 +78,6 @@ export function MinHeightEdit( props ) {
setAttributes,
} = props;
- const units = useCustomUnits( {
- availableUnits: useSetting( 'dimensions.units' ) || [
- '%',
- 'px',
- 'em',
- 'rem',
- 'vh',
- 'vw',
- ],
- } );
-
if ( useIsMinHeightDisabled( props ) ) {
return null;
}
@@ -109,13 +95,10 @@ export function MinHeightEdit( props ) {
};
return (
-
);
}
diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss
index 853a030f045945..7d9af577f32854 100644
--- a/packages/block-editor/src/style.scss
+++ b/packages/block-editor/src/style.scss
@@ -32,6 +32,7 @@
@import "./components/date-format-picker/style.scss";
@import "./components/duotone-control/style.scss";
@import "./components/font-appearance-control/style.scss";
+@import "./components/height-control/style.scss";
@import "./components/image-size-control/style.scss";
@import "./components/inner-blocks/style.scss";
@import "./components/inserter-list-item/style.scss";
diff --git a/packages/edit-site/src/components/global-styles/dimensions-panel.js b/packages/edit-site/src/components/global-styles/dimensions-panel.js
index d27a109e2b752a..1b88bc4074dc87 100644
--- a/packages/edit-site/src/components/global-styles/dimensions-panel.js
+++ b/packages/edit-site/src/components/global-styles/dimensions-panel.js
@@ -18,6 +18,7 @@ import {
} from '@wordpress/components';
import {
__experimentalUseCustomSides as useCustomSides,
+ __experimentalHeightControl as HeightControl,
__experimentalSpacingSizesControl as SpacingSizesControl,
} from '@wordpress/block-editor';
import { Icon, positionCenter, stretchWide } from '@wordpress/icons';
@@ -556,19 +557,15 @@ export default function DimensionsPanel( { name } ) {
) }
{ showMinHeightControl && (
-
) }