Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
6436944
Block API: Add block visibility control support and UI
t-hamano Aug 8, 2025
c12c3cf
Merge branch 'trunk' into block-visibility
t-hamano Aug 15, 2025
7f8d055
Render hidden block block
t-hamano Aug 15, 2025
52913e1
Remove unused code
t-hamano Aug 15, 2025
a5d0146
Always hide block when it's not selected
t-hamano Aug 18, 2025
c3f7135
Extend existing isBlockVisible selector and use it
t-hamano Aug 18, 2025
5f459c7
Revert "Extend existing isBlockVisible selector and use it"
t-hamano Aug 18, 2025
1a46d6b
Remove SCSS import statement
t-hamano Aug 18, 2025
6577955
Use "isBlockHidden"
t-hamano Aug 18, 2025
19b7b9c
Remove useBlockVisibility hook
t-hamano Aug 18, 2025
b1dad49
useBlockDropZone: Filter out blocks that are hidden
t-hamano Aug 18, 2025
6e42b65
Remove useBlockVisibility hook
t-hamano Aug 18, 2025
7849d2f
Update doc
t-hamano Aug 18, 2025
2bfa54e
Show hidden block placeholder when dragging
t-hamano Aug 18, 2025
913ad06
Revert "Show hidden block placeholder when dragging"
t-hamano Aug 19, 2025
8c5c625
Show block toolbar button only when editing mode is default
t-hamano Aug 19, 2025
d8dd684
Merge branch 'trunk' into block-visibility
t-hamano Aug 25, 2025
fb2d668
Process metadata updates for multiple blocks at once
t-hamano Aug 25, 2025
03c1826
Preserve block visibility metadata in block transforms
t-hamano Aug 25, 2025
9c12705
Merge branch 'trunk' into block-visibility
t-hamano Sep 16, 2025
a78bdd5
Remove support from schema and docs
t-hamano Sep 16, 2025
599ed80
Add experiments setting
t-hamano Sep 16, 2025
6c04b64
Make Block Visibility as experiment feature
t-hamano Sep 16, 2025
e54603e
Rename __experimentalBlockVisibility to blockVisibility
t-hamano Sep 18, 2025
854b5d5
Merge branch 'trunk' into block-visibility
t-hamano Sep 30, 2025
3b152ca
Fix: other metadata attributes get stripped when toggling via the too…
t-hamano Sep 30, 2025
fffcf35
Show block toolbar button when multiple blocks are selected
t-hamano Sep 30, 2025
1c9b407
Fix incorrect block.json definition
t-hamano Sep 30, 2025
5596f83
Apply opacity to hidden and selected blocks
t-hamano Sep 30, 2025
c39d72a
Revert "Apply opacity to hidden and selected blocks"
t-hamano Sep 30, 2025
7de5b65
Provide blockVilibility metadata to preview context
t-hamano Sep 30, 2025
f773c15
Accordion Header, Accordion Panel: disable blockVisibility support
t-hamano Sep 30, 2025
f36f529
Visually hide block
t-hamano Sep 30, 2025
f5c1572
Merge branch 'trunk' into block-visibility
t-hamano Oct 1, 2025
bb9ec37
Stabilize feature
t-hamano Oct 1, 2025
4c7338c
Add backport changelog
t-hamano Oct 1, 2025
861689c
Update server side function
t-hamano Oct 1, 2025
0929559
Use "visibility:hidden" to visually hide blocks
t-hamano Oct 1, 2025
dd0d1cb
Combine multiple useSelect call into one
t-hamano Oct 1, 2025
96428fd
Reset styles from hidden blocks
t-hamano Oct 1, 2025
465777d
Update lib/block-supports/block-visibility.php
t-hamano Oct 1, 2025
ff7a8d5
Better behavior for hidden blocks
t-hamano Oct 1, 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
26 changes: 26 additions & 0 deletions docs/reference-guides/block-api/block-supports.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,32 @@ attributes: {
}
```

## blockVisibility
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should change this to visibility and the block part, which seems redundant.

P.S. The block editor already uses similar terminology, isBlockVisible and setBlockVisibility, to track if the block is visible in the canvas viewport. It would be nice to avoid confusion, but I can't think of better terminology.

Copy link
Member

Choose a reason for hiding this comment

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

🤔 I forgot about isBlockVisible - thanks for pointing it out. I originally suggested something other than visibility as it's too close the name of a CSS property, and could clash semantically if it block supports ever gets it.

We could remove the ambiguity and select a different word. The current isBlockVisible tells the system whether the block is visible, whereas we're talking about a user-driven setting. What does the user want to do? temporarily hide blocks (otherwise they'd be deleted) and to stop them rendering on the frontend. It's a bit nuanced, swinging between _ prevent from being seen_ and switch off.

Some alternatives

  • blockDisable/disableBlock
  • blockMasking
  • blockConceal
  • blockObscurity
  • blockCloaking
  • blockSuppress

There might be a PhD thesis here somewhere 😆

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't have a strong opinion on which terminology (visibility or blockVisibility) is better, but the existing isBlockVisible is indeed confusing. There is a proposal to rename the API: #71203 (comment)


_**Note:** Since WordPress 6.9._

- Type: `boolean`
- Default value: `true`

By default, a block can be hidden within the editor from the block "Options" dropdown or the block toolbar. To disable this behavior for a block, set `blockVisibility` to `false`.

```js
supports: {
// Don't allow the block to be hidden via the editor UI.
blockVisibility: false
}
```

When a block is hidden, the state is stored in the block's `metadata` attribute:

```js
attributes: {
metadata: {
blockVisibility: false
}
}
```

## className

- Type: `boolean`
Expand Down
6 changes: 3 additions & 3 deletions docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Reuse this design across your site. ([Source](https://github.com/WordPress/guten

- **Name:** core/block
- **Category:** reusable
- **Supports:** interactivity (clientNavigation), ~~customClassName~~, ~~html~~, ~~inserter~~, ~~renaming~~
- **Supports:** interactivity (clientNavigation), ~~blockVisibility~~, ~~customClassName~~, ~~html~~, ~~inserter~~, ~~renaming~~
- **Attributes:** content, ref

## Button
Expand Down Expand Up @@ -585,7 +585,7 @@ Show a block pattern. ([Source](https://github.com/WordPress/gutenberg/tree/trun

- **Name:** core/pattern
- **Category:** theme
- **Supports:** interactivity (clientNavigation), ~~html~~, ~~inserter~~, ~~renaming~~
- **Supports:** interactivity (clientNavigation), ~~blockVisibility~~, ~~html~~, ~~inserter~~, ~~renaming~~
- **Attributes:** slug

## Author
Expand Down Expand Up @@ -975,7 +975,7 @@ Edit the different global regions of your site, like the header, footer, sidebar

- **Name:** core/template-part
- **Category:** theme
- **Supports:** align, interactivity (clientNavigation), ~~html~~, ~~renaming~~, ~~reusable~~
- **Supports:** align, interactivity (clientNavigation), ~~blockVisibility~~, ~~html~~, ~~renaming~~, ~~reusable~~
- **Attributes:** area, slug, tagName, theme

## Term Description
Expand Down
27 changes: 27 additions & 0 deletions lib/block-supports/block-visibility.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* Block visibility block support flag.
*
* @package gutenberg
*/

/**
* Render nothing if the block is hidden.
*
* @param string $block_content The block content.
* @param array $block The block.
*
* @return string The block content.
*/
function gutenberg_render_block_visibility_support( $block_content, $block ) {
if ( isset( $block['attrs']['metadata']['blockVisibility'] ) && false === $block['attrs']['metadata']['blockVisibility'] ) {
return '';
}

return $block_content;
}

if ( function_exists( 'wp_render_block_visibility_support' ) ) {
remove_filter( 'render_block', 'wp_render_block_visibility_support' );
}
add_filter( 'render_block', 'gutenberg_render_block_visibility_support', 10, 2 );
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/block-supports/background.php';
require __DIR__ . '/block-supports/block-style-variations.php';
require __DIR__ . '/block-supports/aria-label.php';
require __DIR__ . '/block-supports/block-visibility.php';

// Data views.
require_once __DIR__ . '/experimental/data-views.php';
Expand Down
14 changes: 14 additions & 0 deletions packages/block-editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,9 @@ function BlockListBlockProvider( props ) {
return previewContext;
}

const { isBlockHidden: _isBlockHidden } = unlock(
select( blockEditorStore )
);
const _isSelected = isBlockSelected( clientId );
const canRemove = canRemoveBlock( clientId );
const canMove = canMoveBlock( clientId );
Expand Down Expand Up @@ -705,6 +708,7 @@ function BlockListBlockProvider( props ) {
originalBlockClientId: isInvalid
? blocksWithSameName[ 0 ]
: false,
isBlockHidden: _isBlockHidden( clientId ),
};
},
[ clientId, rootClientId ]
Expand Down Expand Up @@ -747,6 +751,7 @@ function BlockListBlockProvider( props ) {
className,
defaultClassName,
originalBlockClientId,
isBlockHidden,
} = selectedProps;

// Users of the editor.BlockListBlock filter used to be able to
Expand Down Expand Up @@ -797,6 +802,15 @@ function BlockListBlockProvider( props ) {
canMove,
};

if (
isBlockHidden &&
! isSelected &&
! isMultiSelected &&
! hasChildSelected
) {
return null;
}

// Here we separate between the props passed to BlockListBlock and any other
// information we selected for internal use. BlockListBlock is a filtered
// component and thus ALL the props are PUBLIC API.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
MenuGroup,
__experimentalStyleProvider as StyleProvider,
} from '@wordpress/components';
import { hasBlockSupport } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';

/**
Expand All @@ -20,13 +21,20 @@ import { store as blockEditorStore } from '../../store';
import BlockModeToggle from '../block-settings-menu/block-mode-toggle';
import { ModifyContentLockMenuItem } from '../content-lock';
import { BlockRenameControl, useBlockRename } from '../block-rename';
import { BlockVisibilityMenuItem } from '../block-visibility';

const { Fill, Slot } = createSlotFill( 'BlockSettingsMenuControls' );

const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => {
const { selectedBlocks, selectedClientIds, isContentOnly } = useSelect(
const {
selectedBlocks,
selectedClientIds,
isContentOnly,
canToggleSelectedBlocksVisibility,
} = useSelect(
( select ) => {
const {
getBlocksByClientId,
getBlockNamesByClientId,
getSelectedBlockClientIds,
getBlockEditingMode,
Expand All @@ -38,6 +46,11 @@ const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => {
selectedClientIds: ids,
isContentOnly:
getBlockEditingMode( ids[ 0 ] ) === 'contentOnly',
canToggleSelectedBlocksVisibility: getBlocksByClientId(
ids
).every( ( block ) =>
hasBlockSupport( block.name, 'blockVisibility', true )
),
};
},
[ clientIds ]
Expand All @@ -49,6 +62,8 @@ const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => {
selectedClientIds.length === 1 && canLock && ! isContentOnly;
const showRenameButton =
selectedClientIds.length === 1 && canRename && ! isContentOnly;
const showVisibilityButton =
canToggleSelectedBlocksVisibility && ! isContentOnly;

// Check if current selection of blocks is Groupable or Ungroupable
// and pass this props down to ConvertToGroupButton.
Expand Down Expand Up @@ -93,6 +108,11 @@ const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => {
clientId={ selectedClientIds[ 0 ] }
/>
) }
{ showVisibilityButton && (
<BlockVisibilityMenuItem
clientIds={ selectedClientIds }
/>
) }
{ fills }
{ selectedClientIds.length === 1 && (
<ModifyContentLockMenuItem
Expand Down
10 changes: 10 additions & 0 deletions packages/block-editor/src/components/block-toolbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import BlockControls from '../block-controls';
import __unstableBlockToolbarLastItem from './block-toolbar-last-item';
import BlockSettingsMenu from '../block-settings-menu';
import { BlockLockToolbar } from '../block-lock';
import { BlockVisibilityToolbar } from '../block-visibility';
import { BlockGroupToolbar } from '../convert-to-group-buttons';
import BlockEditVisuallyButton from '../block-edit-visually-button';
import { useShowHoveredOrFocusedGestures } from './utils';
Expand Down Expand Up @@ -73,6 +74,7 @@ export function PrivateBlockToolbar( {
showSlots,
showGroupButtons,
showLockButtons,
showBlockVisibilityButton,
showSwitchSectionStyleButton,
hasFixedToolbar,
isNavigationMode,
Expand Down Expand Up @@ -153,6 +155,7 @@ export function PrivateBlockToolbar( {
showSlots: ! _isZoomOut,
showGroupButtons: ! _isZoomOut,
showLockButtons: ! _isZoomOut,
showBlockVisibilityButton: ! _isZoomOut,
showSwitchSectionStyleButton:
_isZoomOut ||
( isNavigationModeEnabled &&
Expand Down Expand Up @@ -221,6 +224,13 @@ export function PrivateBlockToolbar( {
>
<ToolbarGroup className="block-editor-block-toolbar__block-controls">
<BlockSwitcher clientIds={ blockClientIds } />
{ ! isMultiToolbar &&
isDefaultEditingMode &&
showBlockVisibilityButton && (
<BlockVisibilityToolbar
clientId={ blockClientId }
/>
) }
{ ! isMultiToolbar &&
isDefaultEditingMode &&
showLockButtons && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as BlockVisibilityMenuItem } from './menu-item';
export { default as BlockVisibilityToolbar } from './toolbar';
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { MenuItem } from '@wordpress/components';
import { seen, unseen } from '@wordpress/icons';
import { useSelect, useDispatch } from '@wordpress/data';

/**
* Internal dependencies
*/
import { cleanEmptyObject } from '../../hooks/utils';
import { store as blockEditorStore } from '../../store';

export default function BlockVisibilityMenuItem( { clientIds } ) {
const { updateBlockAttributes } = useDispatch( blockEditorStore );
const blocks = useSelect(
( select ) => {
return select( blockEditorStore ).getBlocksByClientId( clientIds );
},
[ clientIds ]
);

const hasHiddenBlock = blocks.some(
( block ) => block.attributes.metadata?.blockVisibility === false
);

const toggleBlockVisibility = () => {
blocks.forEach( ( block ) => {
updateBlockAttributes( block.clientId, {
metadata: cleanEmptyObject( {
...block.attributes?.metadata,
blockVisibility: hasHiddenBlock ? undefined : false,
} ),
} );
} );
};

return (
<MenuItem
icon={ hasHiddenBlock ? seen : unseen }
onClick={ toggleBlockVisibility }
>
{ hasHiddenBlock ? __( 'Show' ) : __( 'Hide' ) }
</MenuItem>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { ToolbarButton, ToolbarGroup } from '@wordpress/components';
import { useRef, useEffect } from '@wordpress/element';
import { seen, unseen } from '@wordpress/icons';
import { useSelect, useDispatch } from '@wordpress/data';
import { hasBlockSupport } from '@wordpress/blocks';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
import { unlock } from '../../lock-unlock';

export default function BlockVisibilityToolbar( { clientId } ) {
const { canToggleBlockVisibility, isBlockHidden } = useSelect(
( select ) => {
const { getBlockName } = select( blockEditorStore );
const { isBlockHidden: _isBlockHidden } = unlock(
select( blockEditorStore )
);
return {
canToggleBlockVisibility: hasBlockSupport(
getBlockName( clientId ),
'blockVisibility',
true
),
isBlockHidden: _isBlockHidden( clientId ),
};
},
[ clientId ]
);
const hasBlockVisibilityButtonShownRef = useRef( false );
const { updateBlockAttributes } = useDispatch( blockEditorStore );

// If the block visibility button has been shown, we don't want to
// remove it from the toolbar until the toolbar is rendered again
// without it. Removing it beforehand can cause focus loss issues.
// It needs to return focus from whence it came, and to do that,
// we need to leave the button in the toolbar.
useEffect( () => {
if ( isBlockHidden ) {
hasBlockVisibilityButtonShownRef.current = true;
}
}, [ isBlockHidden ] );

if ( ! isBlockHidden && ! hasBlockVisibilityButtonShownRef.current ) {
return null;
}

const label = isBlockHidden ? __( 'Show' ) : __( 'Hide' );

return (
<>
<ToolbarGroup className="block-editor-block-lock-toolbar">
<ToolbarButton
disabled={ ! canToggleBlockVisibility }
icon={ isBlockHidden ? unseen : seen }
label={ label }
onClick={ () => {
const newBlockHidden = ! isBlockHidden;
updateBlockAttributes( [ clientId ], {
metadata: {
blockVisibility: newBlockHidden
? false
: undefined,
},
} );
} }
/>
</ToolbarGroup>
</>
);
}
Loading
Loading