Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1ea311f
Add Line Indent support.
mtias Nov 8, 2025
3c27f5a
Fix linting errors in theme.json class
aaronrobertshaw Nov 10, 2025
dc05a6a
Add since comments detailing addition of new support and elements
aaronrobertshaw Nov 10, 2025
5879f57
Update global settings and styles docs
aaronrobertshaw Nov 10, 2025
d2a0f21
Tweak intial position for range control slider
aaronrobertshaw Nov 10, 2025
e9e13cb
Make text indent show by default as expected in global styles panel
aaronrobertshaw Nov 10, 2025
8dd95c3
Remove __experimental prefix from block support key
aaronrobertshaw Nov 27, 2025
5555bb9
Tweak organisation and layout of TypographyPanel
aaronrobertshaw Nov 27, 2025
43fa012
Update theme.json schema
aaronrobertshaw Nov 27, 2025
9cc4ad6
Add change backlog
aaronrobertshaw Dec 1, 2025
7c27855
Update docs
aaronrobertshaw Dec 2, 2025
052b246
Try reverting moving text element to under elements
aaronrobertshaw Dec 4, 2025
c133900
Trial adding paragraph element
aaronrobertshaw Dec 10, 2025
691473b
Fix linting errors
aaronrobertshaw Dec 15, 2025
627a579
Fix incorrect unit test data structure
aaronrobertshaw Dec 15, 2025
7e206f0
Revert "Fix incorrect unit test data structure"
aaronrobertshaw Dec 15, 2025
ca915f8
Revert "Fix linting errors"
aaronrobertshaw Dec 15, 2025
32725df
Revert "Trial adding paragraph element"
aaronrobertshaw Dec 15, 2025
bfcd6d6
Restrict line indent support to blocks only
aaronrobertshaw Dec 15, 2025
a663651
Update block.json schema and paragraph selectors
aaronrobertshaw Dec 16, 2025
2ab7730
Remove redundant handling of unsupported textIndent
aaronrobertshaw Dec 18, 2025
364cc33
Clean up handling of paragraph block vs text element textIndent style…
aaronrobertshaw Dec 18, 2025
dad26d4
Fix linting errors
aaronrobertshaw Dec 18, 2025
34b7954
Improve text indent help text for block inspector
aaronrobertshaw Dec 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backport-changelog/7.0/10573.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
https://github.com/WordPress/wordpress-develop/pull/10573

* https://github.com/WordPress/gutenberg/pull/73114
1 change: 1 addition & 0 deletions docs/how-to-guides/themes/global-settings-and-styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ The settings section has the following structure:
"textAlign": true,
"textColumns": false,
"textDecoration": true,
"textIndent": true,
"textTransform": true
},
"blocks": {
Expand Down
2 changes: 1 addition & 1 deletion docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ Start with the basic building block of all narrative. ([Source](https://github.c

- **Name:** core/paragraph
- **Category:** text
- **Supports:** __unstablePasteTextInline, align (full, wide), anchor, color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), splitting, typography (fitText, fontSize, lineHeight, textAlign), ~~className~~
- **Supports:** __unstablePasteTextInline, align (full, wide), anchor, color (background, gradients, link, text), interactivity (clientNavigation), spacing (margin, padding), splitting, typography (fitText, fontSize, lineHeight, textAlign, textIndent), ~~className~~
- **Attributes:** content, direction, dropCap, placeholder

## Pattern Placeholder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ Settings related to typography.
| fluid | Enables fluid typography and allows users to set global fluid typography parameters. | `boolean`, `{ minFontSize, maxViewportWidth, minViewportWidth }` | `false` |
| letterSpacing | Allow users to set custom letter spacing. | `boolean` | `true` |
| lineHeight | Allow users to set custom line height. | `boolean` | `false` |
| textIndent | Allow users to set custom line indent. | `boolean` | `false` |
| textAlign | Allow users to set the text align. | `boolean` | `true` |
| textColumns | Allow users to set the number of text columns. | `boolean` | `false` |
| textDecoration | Allow users to set custom text decorations. | `boolean` | `true` |
Expand Down Expand Up @@ -329,6 +330,7 @@ Typography styles.
| fontWeight | Sets the `font-weight` CSS property. | `string`, `{ ref }` |
| letterSpacing | Sets the `letter-spacing` CSS property. | `string`, `{ ref }` |
| lineHeight | Sets the `line-height` CSS property. | `string`, `{ ref }` |
| textIndent | Sets the `text-indent` CSS property. | `string`, `{ ref }` |
| textAlign | Sets the `text-align` CSS property. | `string`, `{ ref }` |
| textColumns | Sets the `column-count` CSS property. | `string`, `{ ref }` |
| textDecoration | Sets the `text-decoration` CSS property. | `string`, `{ ref }` |
Expand Down
8 changes: 8 additions & 0 deletions lib/block-supports/typography.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function gutenberg_register_typography_support( $block_type ) {
$has_text_columns_support = $typography_supports['textColumns'] ?? false;
$has_text_decoration_support = $typography_supports['__experimentalTextDecoration'] ?? false;
$has_text_transform_support = $typography_supports['__experimentalTextTransform'] ?? false;
$has_text_indent_support = $typography_supports['textIndent'] ?? false;
$has_writing_mode_support = $typography_supports['__experimentalWritingMode'] ?? false;

$has_typography_support = $has_font_family_support
Expand All @@ -42,6 +43,7 @@ function gutenberg_register_typography_support( $block_type ) {
|| $has_text_columns_support
|| $has_text_decoration_support
|| $has_text_transform_support
|| $has_text_indent_support
|| $has_writing_mode_support;

if ( ! $block_type->attributes ) {
Expand Down Expand Up @@ -101,6 +103,7 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) {
$has_text_columns_support = $typography_supports['textColumns'] ?? false;
$has_text_decoration_support = $typography_supports['__experimentalTextDecoration'] ?? false;
$has_text_transform_support = $typography_supports['__experimentalTextTransform'] ?? false;
$has_text_indent_support = $typography_supports['textIndent'] ?? false;
$has_writing_mode_support = $typography_supports['__experimentalWritingMode'] ?? false;

// Whether to skip individual block support features.
Expand All @@ -114,6 +117,7 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) {
$should_skip_text_decoration = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textDecoration' );
$should_skip_text_transform = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textTransform' );
$should_skip_letter_spacing = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'letterSpacing' );
$should_skip_text_indent = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textIndent' );
$should_skip_writing_mode = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'writingMode' );

$typography_block_styles = array();
Expand Down Expand Up @@ -174,6 +178,10 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) {
$typography_block_styles['writingMode'] = $block_attributes['style']['typography']['writingMode'] ?? null;
}

if ( $has_text_indent_support && ! $should_skip_text_indent && isset( $block_attributes['style']['typography']['textIndent'] ) ) {
$typography_block_styles['textIndent'] = $block_attributes['style']['typography']['textIndent'] ?? null;
}

$attributes = array();
$classnames = array();
$styles = gutenberg_style_engine_get_styles(
Expand Down
13 changes: 10 additions & 3 deletions lib/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ class WP_Theme_JSON_Gutenberg {
* @since 6.2.0 Added `outline-*`, and `min-height` properties.
* @since 6.3.0 Added `writing-mode` property.
* @since 6.6.0 Added `background-[image|position|repeat|size]` properties.
* @since 7.0.0 Added `dimensions.width` and `dimensions.height`.
* @since 7.0.0 Added `dimensions.width`, `dimensions.height`. and
* `text-indent` properties.
*
* @var array
*/
Expand Down Expand Up @@ -305,6 +306,7 @@ class WP_Theme_JSON_Gutenberg {
'--wp--style--root--padding-left' => array( 'spacing', 'padding', 'left' ),
'text-decoration' => array( 'typography', 'textDecoration' ),
'text-transform' => array( 'typography', 'textTransform' ),
'text-indent' => array( 'typography', 'textIndent' ),
'filter' => array( 'filter', 'duotone' ),
'box-shadow' => array( 'shadow' ),
'height' => array( 'dimensions', 'height' ),
Expand Down Expand Up @@ -398,7 +400,8 @@ class WP_Theme_JSON_Gutenberg {
* @since 6.4.0 Added `layout.allowEditing`.
* @since 6.4.0 Added `lightbox`.
* @since 7.0.0 Added type markers to the schema for boolean values.
* @since 7.0.0 Added `dimensions.width` and `dimensions.height`.
* @since 7.0.0 Added `dimensions.width`, `dimensions.height`. and
* `text-indent` properties.
* @var array
*/
const VALID_SETTINGS = array(
Expand Down Expand Up @@ -484,6 +487,7 @@ class WP_Theme_JSON_Gutenberg {
'textAlign' => null,
'textColumns' => null,
'textDecoration' => null,
'textIndent' => null,
'textTransform' => null,
'writingMode' => null,
),
Expand Down Expand Up @@ -527,7 +531,8 @@ class WP_Theme_JSON_Gutenberg {
* @since 6.2.0 Added `outline`, and `minHeight` properties.
* @since 6.6.0 Added `background` sub properties to top-level only.
* @since 6.6.0 Added `dimensions.aspectRatio`.
* @since 7.0.0 Added `dimensions.width` and `dimensions.height`.
* @since 7.0.0 Added `dimensions.width`, `dimensions.height`. and
* `text-indent` properties.
* @var array
*/
const VALID_STYLES = array(
Expand Down Expand Up @@ -584,6 +589,7 @@ class WP_Theme_JSON_Gutenberg {
'textAlign' => null,
'textColumns' => null,
'textDecoration' => null,
'textIndent' => null,
'textTransform' => null,
'writingMode' => null,
),
Expand Down Expand Up @@ -625,6 +631,7 @@ class WP_Theme_JSON_Gutenberg {
*
* @since 5.8.0
* @since 6.1.0 Added `heading`, `button`, and `caption` elements.
* @since 7.0.0 Added `text` property.
* @var string[]
*/
const ELEMENTS = array(
Expand Down
1 change: 1 addition & 0 deletions lib/theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@
"textAlign": true,
"textColumns": false,
"textDecoration": true,
"textIndent": true,
"textTransform": true,
"writingMode": false
},
Expand Down
9 changes: 9 additions & 0 deletions packages/block-editor/src/components/global-styles/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export function useSettingsForBlockElement(
'textAlign',
'textTransform',
'textDecoration',
'textIndent',
'writingMode',
].forEach( ( key ) => {
if ( ! supportedStyles.includes( key ) ) {
Expand All @@ -110,6 +111,14 @@ export function useSettingsForBlockElement(
}
} );

// Text indent needs explicit handling since it may not be in parent settings.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea why this PR is causing the e2e fail.

The fail occurs in the should apply custom colors and font sizes in a variation test, and the fontSize value wasn't coming through to the control.

Here's what my agent is telling me:

if the PR added code that initializes or ensures styles.elements.text exists in the base (e.g., when textIndent is supported), then the shallow merge would drop it when a variation is applied.

So, doing a deep merge in the variation seems to fix it:

diff --git a/packages/global-styles-ui/src/variations/variation.tsx b/packages/global-styles-ui/src/variations/variation.tsx
index ace9f1d47fd..4f0263a804c 100644
--- a/packages/global-styles-ui/src/variations/variation.tsx
+++ b/packages/global-styles-ui/src/variations/variation.tsx
@@ -10,7 +10,10 @@ import { Tooltip } from '@wordpress/components';
 import { useMemo, useContext, useState } from '@wordpress/element';
 import { ENTER } from '@wordpress/keycodes';
 import { _x, sprintf } from '@wordpress/i18n';
-import { areGlobalStylesEqual } from '@wordpress/global-styles-engine';
+import {
+	areGlobalStylesEqual,
+	mergeGlobalStyles,
+} from '@wordpress/global-styles-engine';
 
 /**
  * Internal dependencies
@@ -41,7 +44,7 @@ export default function Variation( {
 	} = useContext( GlobalStylesContext );
 
 	const context = useMemo( () => {
-		let merged = { ...base, ...variation };
+		let merged = mergeGlobalStyles( base, variation );
 		if ( properties ) {
 			merged = filterObjectByProperties( merged, properties );
 		}

I suppose because there are nested objects that need to be merged there, e.g., base.styles.elements.text with variation.styles.elements.text

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait, now it doesn't pass again. Sorry for the red herring. I swear it passed on this change!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay it's because I had another suggested fix applied, one that does, I hope, work:

diff --git a/packages/global-styles-engine/src/settings/get-style.ts b/packages/global-styles-engine/src/settings/get-style.ts
index 411888e023a..b2ef29dc5a5 100644
--- a/packages/global-styles-engine/src/settings/get-style.ts
+++ b/packages/global-styles-engine/src/settings/get-style.ts
@@ -11,13 +11,58 @@ export function getStyle< T = any >(
 	blockName?: string,
 	shouldDecodeEncode = true
 ): T | undefined {
+	if ( ! globalStyles ) {
+		return undefined;
+	}
+
+	// When querying element paths like 'elements.text', also check root-level styles
+	// as root-level styles apply to the text element
+	if ( path && path.startsWith( 'elements.' ) ) {
+		const elementPath = path.split( '.' );
+		const elementName = elementPath[ 1 ];
+
+		// First try the element-specific path
+		const elementValue = getValueFromObjectPath( globalStyles, [
+			'styles',
+			...elementPath,
+		] );
+		if ( elementValue !== undefined ) {
+			return (
+				shouldDecodeEncode
+					? getValueFromVariable(
+							globalStyles,
+							blockName,
+							elementValue as UnresolvedValue
+					  )
+					: elementValue
+			) as T | undefined;
+		}
+
+		// Fallback to root-level styles for text element
+		if ( elementName === 'text' ) {
+			const rootValue = getValueFromObjectPath( globalStyles, [
+				'styles',
+			] );
+			if ( rootValue !== undefined ) {
+				return (
+					shouldDecodeEncode
+						? getValueFromVariable(
+								globalStyles,
+								blockName,
+								rootValue as UnresolvedValue
+						  )
+						: rootValue
+				) as T | undefined;
+			}
+		}
+
+		return undefined;
+	}
+
 	const appendedPath = path ? '.' + path : '';
 	const finalPath = ! blockName
 		? `styles${ appendedPath }`
 		: `styles.blocks.${ blockName }${ appendedPath }`;
-	if ( ! globalStyles ) {
-		return undefined;
-	}
 
 	const rawResult = getValueFromObjectPath( globalStyles, finalPath ) as
 		| string

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I came to the same conclusion, i.e. the move of text element styles from styles.typography etc to styles.elements.text.typography was the culprit.

What I can't answer just yet was why that chance was made in this PR.

The fallback approach should work but I'm interested to know if we really need to move the styles at all and increase complexity here as a result.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah it would be nice to know why that was the choice. there must be a architectural reason.

i can't get much out of the pr description.

cc @mtias if you can provide more info

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've started exploring some of the suggested options here. Starting with a paragraphs element. It might be easier to discuss with a working PoC.

It's rough and incomplete at this stage but a draft has been added via: 70e7e07

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaronrobertshaw if we are looking at a paragraph element, I think it may be an indication that we should just use the paragraph block instead. That may be more straightforward since "text" is meant to apply to things like lists, etc, which are not applicable here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mtias for the extra insight.

My thinking on the paragraph element was allowing this line indent style to apply across block types. Having played with this some, I'm not convinced there's a lot of benefit to it. Any third-party blocks wanting to allow this could probably be using paragraph blocks as inner blocks anyway.

If we lean towards just using the paragraph block, this does become much simpler. When adopting this new block support a custom selector can be set via the Block Selectors API for the line indent support only.

I'll get this updated to revert the paragraph element changes and lean more on the paragraph block only.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! We just need to handle the contiguous paragraph case elegantly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pushed the promised updates. The paragraph block uses the p + p selector for the textIndent block support feature only.

So for global styles this will work almost the same as the first approach in this PR. As it stands now, defining text indent on the paragraph block will result in styles using the :root :where(p + p) selector.

if ( supportedStyles.includes( 'textIndent' ) ) {
updatedSettings.typography = {
...updatedSettings.typography,
textIndent: updatedSettings.typography?.textIndent ?? true,
};
}

// The column-count style is named text column to reduce confusion with
// the columns block and manage expectations from the support.
// See: https://github.com/WordPress/gutenberg/pull/33587
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import LetterSpacingControl from '../letter-spacing-control';
import TextAlignmentControl from '../text-alignment-control';
import TextTransformControl from '../text-transform-control';
import TextDecorationControl from '../text-decoration-control';
import TextIndentControl from '../text-indent-control';
import WritingModeControl from '../writing-mode-control';
import { useToolsPanelDropdownMenuProps } from './utils';
import { setImmutably } from '../../utils/object';
Expand All @@ -42,6 +43,7 @@ export function useHasTypographyPanel( settings ) {
const hasTextAlign = useHasTextAlignmentControl( settings );
const hasTextTransform = useHasTextTransformControl( settings );
const hasTextDecoration = useHasTextDecorationControl( settings );
const hasTextIndent = useHasTextIndentControl( settings );
const hasWritingMode = useHasWritingModeControl( settings );
const hasTextColumns = useHasTextColumnsControl( settings );
const hasFontSize = useHasFontSizeControl( settings );
Expand All @@ -55,6 +57,7 @@ export function useHasTypographyPanel( settings ) {
hasTextTransform ||
hasFontSize ||
hasTextDecoration ||
hasTextIndent ||
hasWritingMode ||
hasTextColumns
);
Expand Down Expand Up @@ -118,6 +121,10 @@ function useHasTextColumnsControl( settings ) {
return settings?.typography?.textColumns;
}

function useHasTextIndentControl( settings ) {
return settings?.typography?.textIndent;
}

/**
* Concatenate all the font sizes into a single list for the font size picker.
*
Expand Down Expand Up @@ -169,6 +176,7 @@ const DEFAULT_CONTROLS = {
textAlign: true,
textTransform: true,
textDecoration: true,
textIndent: true,
writingMode: true,
textColumns: true,
};
Expand All @@ -181,6 +189,7 @@ export default function TypographyPanel( {
settings,
panelId,
defaultControls = DEFAULT_CONTROLS,
isGlobalStyles = false,
} ) {
const decodeValue = ( rawValue ) =>
getValueFromVariable( { settings }, '', rawValue );
Expand Down Expand Up @@ -358,6 +367,21 @@ export default function TypographyPanel( {
const hasLetterSpacing = () => !! value?.typography?.letterSpacing;
const resetLetterSpacing = () => setLetterSpacing( undefined );

// Text Indent
const hasTextIndentControl = useHasTextIndentControl( settings );
const textIndent = decodeValue( inheritedValue?.typography?.textIndent );
const setTextIndent = ( newValue ) => {
onChange(
setImmutably(
value,
[ 'typography', 'textIndent' ],
newValue || undefined
)
);
};
const hasTextIndent = () => !! value?.typography?.textIndent;
const resetTextIndent = () => setTextIndent( undefined );

// Text Columns
const hasTextColumnsControl = useHasTextColumnsControl( settings );
const textColumns = decodeValue( inheritedValue?.typography?.textColumns );
Expand Down Expand Up @@ -490,7 +514,6 @@ export default function TypographyPanel( {
) }
{ hasAppearanceControl && (
<ToolsPanelItem
className="single-column"
label={ appearanceControlLabel }
hasValue={ hasFontAppearance }
onDeselect={ resetFontAppearance }
Expand Down Expand Up @@ -544,6 +567,30 @@ export default function TypographyPanel( {
/>
</ToolsPanelItem>
) }
{ hasTextIndentControl && (
<ToolsPanelItem
label={ __( 'Line indent' ) }
hasValue={ hasTextIndent }
onDeselect={ resetTextIndent }
isShownByDefault={ defaultControls.textIndent }
panelId={ panelId }
>
<TextIndentControl
value={ textIndent }
onChange={ setTextIndent }
size="__unstable-large"
__unstableInputWidth="auto"
withSlider
help={
isGlobalStyles
? __(
'Indents the first line of each paragraph after the first one.'
)
: __( 'Indents the first line of text.' )
}
/>
</ToolsPanelItem>
) }
{ hasTextColumnsControl && (
<ToolsPanelItem
className="single-column"
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export { default as __experimentalFontAppearanceControl } from './font-appearanc
export { default as __experimentalFontFamilyControl } from './font-family';
export { default as __experimentalLetterSpacingControl } from './letter-spacing-control';
export { default as __experimentalTextDecorationControl } from './text-decoration-control';
export { default as __experimentalTextIndentControl } from './text-indent-control';
export { default as __experimentalTextTransformControl } from './text-transform-control';
export { default as __experimentalWritingModeControl } from './writing-mode-control';
export { default as __experimentalColorGradientControl } from './colors-gradients/control';
Expand Down
Loading
Loading