diff --git a/lib/block-supports/border.php b/lib/block-supports/border.php
new file mode 100644
index 0000000000000..085cdda187da0
--- /dev/null
+++ b/lib/block-supports/border.php
@@ -0,0 +1,89 @@
+attributes ) {
+ $block_type->attributes = array();
+ }
+
+ if ( $has_border_radius_support && ! array_key_exists( 'style', $block_type->attributes ) ) {
+ $block_type->attributes['style'] = array(
+ 'type' => 'object',
+ );
+ }
+}
+
+/**
+ * Adds CSS classes and inline styles for border styles to the incoming
+ * attributes array. This will be applied to the block markup in the front-end.
+ *
+ * @param WP_Block_type $block_type Block type.
+ * @param array $block_attributes Block attributes.
+ *
+ * @return array Border CSS classes and inline styles.
+ */
+function gutenberg_apply_border_support( $block_type, $block_attributes ) {
+ // Arrays used to ease addition of further border related features in future.
+ $styles = array();
+
+ // Border Radius.
+ if ( gutenberg_has_border_support( $block_type, 'radius' ) ) {
+ if ( isset( $block_attributes['style']['border']['radius'] ) ) {
+ $border_radius = intval( $block_attributes['style']['border']['radius'] );
+ $styles[] = sprintf( 'border-radius: %dpx;', $border_radius );
+ }
+ }
+
+ // Border width, style etc can be added here.
+
+ // Collect classes and styles.
+ $attributes = array();
+
+ if ( ! empty( $styles ) ) {
+ $attributes['style'] = implode( ' ', $styles );
+ }
+
+ return $attributes;
+}
+
+/**
+ * Checks whether the current block type supports the feature requested.
+ *
+ * @param WP_Block_Type $block_type Block type to check for support.
+ * @param string $feature Name of the feature to check support for.
+ * @param mixed $default Fallback value for feature support, defaults to false.
+ *
+ * @return boolean Whether or not the feature is supported.
+ */
+function gutenberg_has_border_support( $block_type, $feature, $default = false ) {
+ $block_support = false;
+ if ( property_exists( $block_type, 'supports' ) ) {
+ $block_support = gutenberg_experimental_get( $block_type->supports, array( '__experimentalBorder' ), $default );
+ }
+
+ return true === $block_support || ( is_array( $block_support ) && gutenberg_experimental_get( $block_support, array( $feature ), false ) );
+}
+
+// Register the block support.
+WP_Block_Supports::get_instance()->register(
+ 'border',
+ array(
+ 'register_attribute' => 'gutenberg_register_border_support',
+ 'apply' => 'gutenberg_apply_border_support',
+ )
+);
diff --git a/lib/class-wp-theme-json.php b/lib/class-wp-theme-json.php
index 165589c0a569e..548134f3cd215 100644
--- a/lib/class-wp-theme-json.php
+++ b/lib/class-wp-theme-json.php
@@ -50,6 +50,7 @@ class WP_Theme_JSON {
'--wp--style--color--link',
'background',
'backgroundColor',
+ 'border',
'color',
'fontFamily',
'fontSize',
@@ -114,6 +115,9 @@ class WP_Theme_JSON {
),
),
'settings' => array(
+ 'border' => array(
+ 'customRadius' => null,
+ ),
'color' => array(
'custom' => null,
'customGradient' => null,
@@ -284,6 +288,10 @@ class WP_Theme_JSON {
'value' => array( 'color', 'background' ),
'support' => array( 'color' ),
),
+ 'borderRadius' => array(
+ 'value' => array( 'border', 'radius' ),
+ 'support' => array( '__experimentalBorder' ),
+ ),
'color' => array(
'value' => array( 'color', 'text' ),
'support' => array( 'color' ),
diff --git a/lib/experimental-default-theme.json b/lib/experimental-default-theme.json
index 82c1bccf28e73..b2a1c31a639db 100644
--- a/lib/experimental-default-theme.json
+++ b/lib/experimental-default-theme.json
@@ -250,6 +250,9 @@
"spacing": {
"customPadding": false,
"units": [ "px", "em", "rem", "vh", "vw" ]
+ },
+ "border": {
+ "customRadius": true
}
}
}
diff --git a/lib/load.php b/lib/load.php
index 967c995ee413a..82686d85ca45f 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -135,3 +135,4 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/block-supports/align.php';
require __DIR__ . '/block-supports/typography.php';
require __DIR__ . '/block-supports/custom-classname.php';
+require __DIR__ . '/block-supports/border.php';
diff --git a/packages/block-editor/src/hooks/border-radius.js b/packages/block-editor/src/hooks/border-radius.js
new file mode 100644
index 0000000000000..f9d90b444b640
--- /dev/null
+++ b/packages/block-editor/src/hooks/border-radius.js
@@ -0,0 +1,79 @@
+/**
+ * WordPress dependencies
+ */
+import { getBlockSupport } from '@wordpress/blocks';
+import { RangeControl } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import useEditorFeature from '../components/use-editor-feature';
+import { BORDER_SUPPORT_KEY } from './border';
+import { cleanEmptyObject } from './utils';
+
+const MIN_BORDER_RADIUS_VALUE = 0;
+const MAX_BORDER_RADIUS_VALUE = 50;
+
+/**
+ * Inspector control panel containing the border radius related configuration.
+ *
+ * @param {Object} props Block properties.
+ * @return {WPElement} Border radius edit element.
+ */
+export function BorderRadiusEdit( props ) {
+ const {
+ attributes: { style },
+ setAttributes,
+ } = props;
+
+ if ( useIsBorderRadiusDisabled( props ) ) {
+ return null;
+ }
+
+ const onChange = ( newRadius ) => {
+ const newStyle = {
+ ...style,
+ border: {
+ ...style?.border,
+ radius: newRadius,
+ },
+ };
+
+ setAttributes( { style: cleanEmptyObject( newStyle ) } );
+ };
+
+ return (
+
+ );
+}
+
+/**
+ * Determines if there is border radius support.
+ *
+ * @param {string|Object} blockType Block name or Block Type object.
+ * @return {boolean} Whether there is support.
+ */
+export function hasBorderRadiusSupport( blockType ) {
+ const support = getBlockSupport( blockType, BORDER_SUPPORT_KEY );
+ return true === support || ( support && !! support.radius );
+}
+
+/**
+ * Custom hook that checks if border radius settings have been disabled.
+ *
+ * @param {string} name The name of the block.
+ * @return {boolean} Whether border radius setting is disabled.
+ */
+export function useIsBorderRadiusDisabled( { name: blockName } = {} ) {
+ const isDisabled = ! useEditorFeature( 'border.customRadius' );
+ return ! hasBorderRadiusSupport( blockName ) || isDisabled;
+}
diff --git a/packages/block-editor/src/hooks/border.js b/packages/block-editor/src/hooks/border.js
new file mode 100644
index 0000000000000..228abd4083bfa
--- /dev/null
+++ b/packages/block-editor/src/hooks/border.js
@@ -0,0 +1,67 @@
+/**
+ * WordPress dependencies
+ */
+import { getBlockSupport } from '@wordpress/blocks';
+import { PanelBody } from '@wordpress/components';
+import { Platform } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import InspectorControls from '../components/inspector-controls';
+import { BorderRadiusEdit, useIsBorderRadiusDisabled } from './border-radius';
+
+export const BORDER_SUPPORT_KEY = '__experimentalBorder';
+
+export function BorderPanel( props ) {
+ const isDisabled = useIsBorderDisabled( props );
+ const isSupported = hasBorderSupport( props.name );
+
+ if ( isDisabled || ! isSupported ) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ );
+}
+
+/**
+ * Determine whether there is block support for borders.
+ *
+ * @param {string} blockName Block name.
+ * @return {boolean} Whether there is support.
+ */
+export function hasBorderSupport( blockName ) {
+ if ( Platform.OS !== 'web' ) {
+ return false;
+ }
+
+ const support = getBlockSupport( blockName, BORDER_SUPPORT_KEY );
+
+ // Further border properties to be added in future iterations.
+ // e.g. support && ( support.radius || support.width || support.style )
+ return true === support || ( support && support.radius );
+}
+
+/**
+ * Determines whether there is any block support for borders e.g. border radius,
+ * style, width etc.
+ *
+ * @param {Object} props Block properties.
+ * @return {boolean} If border support is completely disabled.
+ */
+const useIsBorderDisabled = ( props = {} ) => {
+ // Further border properties to be added in future iterations.
+ // e.g. const configs = [
+ // useIsBorderRadiusDisabled( props ),
+ // useIsBorderWidthDisabled( props ),
+ // ];
+ const configs = [ useIsBorderRadiusDisabled( props ) ];
+ return configs.filter( Boolean ).length === configs.length;
+};
diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js
index d533390250cb1..125a850836f10 100644
--- a/packages/block-editor/src/hooks/style.js
+++ b/packages/block-editor/src/hooks/style.js
@@ -16,6 +16,7 @@ import { createHigherOrderComponent } from '@wordpress/compose';
/**
* Internal dependencies
*/
+import { BORDER_SUPPORT_KEY, BorderPanel } from './border';
import { COLOR_SUPPORT_KEY, ColorEdit } from './color';
import { TypographyPanel, TYPOGRAPHY_SUPPORT_KEYS } from './typography';
import { SPACING_SUPPORT_KEY, PaddingEdit } from './padding';
@@ -23,6 +24,7 @@ import SpacingPanelControl from '../components/spacing-panel-control';
const styleSupportKeys = [
...TYPOGRAPHY_SUPPORT_KEYS,
+ BORDER_SUPPORT_KEY,
COLOR_SUPPORT_KEY,
SPACING_SUPPORT_KEY,
];
@@ -156,6 +158,7 @@ export const withBlockControls = createHigherOrderComponent(
return [
,
+ ,
,
,
hasSpacingSupport && (
diff --git a/packages/block-editor/src/hooks/test/style.js b/packages/block-editor/src/hooks/test/style.js
index 533694be7282e..62c5a97b6e211 100644
--- a/packages/block-editor/src/hooks/test/style.js
+++ b/packages/block-editor/src/hooks/test/style.js
@@ -17,9 +17,11 @@ describe( 'getInlineStyles', () => {
getInlineStyles( {
color: { text: 'red', background: 'black' },
typography: { lineHeight: 1.5, fontSize: 10 },
+ border: { radius: 10 },
} )
).toEqual( {
backgroundColor: 'black',
+ borderRadius: 10,
color: 'red',
lineHeight: 1.5,
fontSize: 10,
diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js
index 68a08552f16b0..8612244ff0984 100644
--- a/packages/blocks/src/api/constants.js
+++ b/packages/blocks/src/api/constants.js
@@ -25,6 +25,10 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = {
value: [ 'color', 'background' ],
support: [ 'color' ],
},
+ borderRadius: {
+ value: [ 'border', 'radius' ],
+ support: [ '__experimentalBorder', 'radius' ],
+ },
color: {
value: [ 'color', 'text' ],
support: [ 'color' ],