Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
96e603b
Add support for tooltips for metabox controls
joedolson Jul 14, 2026
d877331
Update template.php
joedolson Jul 14, 2026
1498835
Update text so accessible name matches visible name
joedolson Jul 14, 2026
67a52d8
Merge remote-tracking branch 'upstream/trunk' into add-metabox-tooltips
joedolson Jul 15, 2026
679511c
Add HTML API parsing of button markup.
joedolson Jul 15, 2026
69bf5ff
Add styles to reduce highlighting of tooltips when on disabled buttons
joedolson Jul 15, 2026
87bbb5d
Need to process all buttons
joedolson Jul 15, 2026
1332e47
Using the HTML API changes the order of button attributes
joedolson Jul 15, 2026
a8214be
Merge branch 'trunk' into add-metabox-tooltips
joedolson Jul 19, 2026
59d377a
Merge branch 'trunk' into add-metabox-tooltips
joedolson Jul 19, 2026
235515b
Restore button handling
joedolson Jul 20, 2026
9dbb894
Remove color shift on disabled buttons
joedolson Jul 20, 2026
d75ff00
Try: add role=region & aria-label to widget, simplify labels
joedolson Jul 20, 2026
4b5bb12
Move default values to default args array
joedolson Jul 20, 2026
a31c87c
$id now undefined
joedolson Jul 20, 2026
11955b5
Don't override hover/focus when disabled.
joedolson Jul 20, 2026
7fd7bdb
Update template.php
joedolson Jul 20, 2026
3b78fe9
Add tooltips to media table view switcher links.
afercia Jul 20, 2026
69c4ae8
Only add popovertarget to toggletipds
joedolson Jul 20, 2026
8f593cc
Update tests with new expectations
joedolson Jul 20, 2026
4d24741
Revert "Only add popovertarget to toggletipds"
joedolson Jul 20, 2026
312c59a
Revert "Update tests with new expectations"
joedolson Jul 20, 2026
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: 2 additions & 1 deletion src/js/_enqueues/wp/wp-tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
let openTimeout;

popovers.forEach( function( popover ) {
const trigger = /** @type {HTMLButtonElement|null} */ ( popover.querySelector( 'button.wp-tooltip__toggle' ) );
const trigger = /** @type {HTMLButtonElement|HTMLAnchorElement|null} */ ( popover.querySelector( '.wp-tooltip__toggle' ) );
const panel = /** @type {HTMLSpanElement|null} */ ( popover.querySelector( 'span.wp-tooltip__bubble' ) );

if ( ! trigger || ! panel ) {
return;
}
Expand Down
7 changes: 3 additions & 4 deletions src/wp-admin/css/wp-tooltip.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
.wp-tooltip {
display: inline-flex;
align-items: center;
vertical-align: middle;
}

/* Toggle and close buttons. */
Expand All @@ -22,14 +21,14 @@
cursor: pointer;
}

.wp-tooltip .wp-tooltip__toggle:hover,
.wp-tooltip .wp-tooltip__toggle:focus,
.wp-tooltip:not(:has(button[aria-disabled="true"],button[disabled="true"])) .wp-tooltip__toggle:hover,
.wp-tooltip:not(:has(button[aria-disabled="true"],button[disabled="true"])) .wp-tooltip__toggle:focus,
.wp-tooltip .wp-tooltip__close:hover,
.wp-tooltip .wp-tooltip__close:focus {
color: var(--wp-admin-theme-color, #3858e9);
}

.wp-tooltip .wp-tooltip__toggle:focus,
.wp-tooltip:not(:has(button[aria-disabled="true"],button[disabled="true"])) .wp-tooltip__toggle:focus,
.wp-tooltip .wp-tooltip__close:focus {
outline: 2px solid transparent;
box-shadow: 0 0 0 var(--wp-admin-border-width-focus, 1.5px) var(--wp-admin-theme-color, #3858e9);
Expand Down
19 changes: 13 additions & 6 deletions src/wp-admin/includes/class-wp-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,9 @@ protected function view_switcher( $current_mode ) {
<input type="hidden" name="mode" value="<?php echo esc_attr( $current_mode ); ?>" />
<div class="view-switch">
<?php
wp_enqueue_style( 'wp-tooltip' );
wp_enqueue_script( 'wp-tooltip' );

foreach ( $this->modes as $mode => $title ) {
$classes = array( 'view-' . $mode );
$aria_current = '';
Expand All @@ -818,14 +821,18 @@ protected function view_switcher( $current_mode ) {
$aria_current = ' aria-current="page"';
}

printf(
"<a href='%s' class='%s' id='view-switch-$mode'$aria_current>" .
"<span class='screen-reader-text'>%s</span>" .
"</a>\n",
$switcher_link = sprintf(
"<a href='%s' aria-label='%s' class='%s wp-tooltip__toggle' id='view-switch-$mode'$aria_current></a>\n",
esc_url( remove_query_arg( 'attachment-filter', add_query_arg( 'mode', $mode ) ) ),
implode( ' ', $classes ),
$title
esc_attr( $title ),
implode( ' ', $classes )
);

$switcher_link_args = array(
'id' => "view-switch-$mode-tooltip",
'button' => $switcher_link,
);
echo wp_get_tooltip( $title, $switcher_link_args );
}
?>
</div>
Expand Down
67 changes: 32 additions & 35 deletions src/wp-admin/includes/template.php
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,8 @@ function do_meta_boxes( $screen, $context, $data_object ) {
$i = 0;

if ( isset( $wp_meta_boxes[ $page ][ $context ] ) ) {
wp_enqueue_script( 'wp-tooltip' );
wp_enqueue_style( 'wp-tooltip' );
foreach ( array( 'high', 'sorted', 'core', 'default', 'low' ) as $priority ) {
if ( isset( $wp_meta_boxes[ $page ][ $context ][ $priority ] ) ) {
foreach ( (array) $wp_meta_boxes[ $page ][ $context ][ $priority ] as $box ) {
Expand Down Expand Up @@ -1372,7 +1374,7 @@ function do_meta_boxes( $screen, $context, $data_object ) {
++$i;
// get_hidden_meta_boxes() doesn't apply in the block editor.
$hidden_class = ( ! $screen->is_block_editor() && in_array( $box['id'], $hidden, true ) ) ? ' hide-if-js' : '';
echo '<div id="' . $box['id'] . '" class="postbox ' . postbox_classes( $box['id'], $page ) . $hidden_class . '" ' . '>' . "\n";
echo '<div id="' . $box['id'] . '" class="postbox ' . postbox_classes( $box['id'], $page ) . $hidden_class . '" ' . ' role="region" aria-label="' . esc_attr( $box['title'] ) . '">' . "\n";

echo '<div class="postbox-header">';
echo '<h2 class="hndle">';
Expand All @@ -1397,40 +1399,35 @@ function do_meta_boxes( $screen, $context, $data_object ) {

echo '<div class="handle-actions hide-if-no-js">';

echo '<button type="button" class="handle-order-higher" aria-disabled="false" aria-describedby="' . $box['id'] . '-handle-order-higher-description">';
echo '<span class="screen-reader-text">' .
/* translators: Hidden accessibility text. */
__( 'Move up' ) .
'</span>';
echo '<span class="order-higher-indicator" aria-hidden="true"></span>';
echo '</button>';
echo '<span class="hidden" id="' . $box['id'] . '-handle-order-higher-description">' . sprintf(
/* translators: %s: Meta box title. */
__( 'Move %s box up' ),
$widget_title
) . '</span>';

echo '<button type="button" class="handle-order-lower" aria-disabled="false" aria-describedby="' . $box['id'] . '-handle-order-lower-description">';
echo '<span class="screen-reader-text">' .
/* translators: Hidden accessibility text. */
__( 'Move down' ) .
'</span>';
echo '<span class="order-lower-indicator" aria-hidden="true"></span>';
echo '</button>';
echo '<span class="hidden" id="' . $box['id'] . '-handle-order-lower-description">' . sprintf(
/* translators: %s: Meta box title. */
__( 'Move %s box down' ),
$widget_title
) . '</span>';

echo '<button type="button" class="handlediv" aria-expanded="true">';
echo '<span class="screen-reader-text">' . sprintf(
/* translators: %s: Hidden accessibility text. Meta box title. */
__( 'Show or hide panel: %s' ),
$widget_title
) . '</span>';
echo '<span class="toggle-indicator" aria-hidden="true"></span>';
echo '</button>';
$move_up_button = '<button type="button" class="handle-order-higher">
<span class="screen-reader-text">' . __( 'Move up' ) . '</span>
<span class="order-higher-indicator" aria-hidden="true"></span>
</button>';
$move_up_args = array(
'id' => $box['id'] . '-handle-order-higher-description',
'button' => $move_up_button,
);
echo wp_get_tooltip( __( 'Move up' ), $move_up_args );

$move_down_button = '<button type="button" class="handle-order-lower">
<span class="screen-reader-text">' . __( 'Move down' ) . '</span>
<span class="order-lower-indicator" aria-hidden="true"></span>
</button>';
$move_down_args = array(
'id' => $box['id'] . '-handle-order-lower-description',
'button' => $move_down_button,
);
echo wp_get_tooltip( __( 'Move down' ), $move_down_args );

$show_hide_button = '<button type="button" class="handlediv" aria-expanded="true">
<span class="screen-reader-text">' . __( 'Show or hide panel' ) . '</span>
<span class="toggle-indicator" aria-hidden="true"></span>
</button>';
$show_hide_args = array(
'id' => $box['id'] . '-handlediv',
'button' => $show_hide_button,
);
echo wp_get_tooltip( __( 'Show or hide panel' ), $show_hide_args );

echo '</div>';
}
Expand Down
45 changes: 29 additions & 16 deletions src/wp-includes/general-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,8 @@ function wp_get_toggletip( $content, $args ) {
*
* @type string $id Unique ID for the popover element. Default is a
* generated unique ID.
* @type string $button Existing `button` markup. Used instead of generated button.
* Default empty.
* @type string $label Accessible label for the toggle button.
* Default 'Help', matching the default icon.
* Ignored for tooltips.
Expand All @@ -469,7 +471,8 @@ function wp_get_tooltip_helper( $content, $args = array() ) {
}

$defaults = array(
'id' => '',
'id' => wp_unique_id( 'wp-tooltip-' ),
'button' => '<button type="button" aria-label="%3$s"><span class="dashicons%4$s" aria-hidden="true"></span></button>',
'label' => __( 'Help' ),
'close_label' => __( 'Close' ),
'icon' => 'dashicons-editor-help',
Expand All @@ -479,14 +482,28 @@ function wp_get_tooltip_helper( $content, $args = array() ) {

$args = wp_parse_args( $args, $defaults );

$id = '' !== $args['id'] ? $args['id'] : wp_unique_id( 'wp-tooltip-' );

$classes = ( 'tooltip' === $args['type'] ) ? 'wp-tooltip wp-is-tooltip' : 'wp-tooltip wp-is-toggletip';
if ( '' !== $args['class'] ) {
$classes .= ' ' . $args['class'];
}

$icon = '' !== $args['icon'] ? ' ' . $args['icon'] : '';
$icon = $args['icon'];
$button = $args['button'];
$processor = new WP_HTML_Tag_Processor( $button );

if ( $processor->next_tag( 'button' ) ) {
$processor->set_attribute( 'popovertarget', '%2$s' );
$processor->add_class( 'wp-tooltip__toggle' );
if ( 'tooltip' !== $args['type'] ) {
$processor->set_attribute( 'aria-haspopup', 'dialog' );
}
$button = $processor->get_updated_html();
}

if ( $processor->next_tag( 'a' ) && 'tooltip' === $args['type'] ) {
$processor->add_class( 'wp-tooltip__toggle' );
$button = $processor->get_updated_html();
}

/*
* The markup only uses phrasing content so it is valid when nested
Expand All @@ -498,16 +515,14 @@ function wp_get_tooltip_helper( $content, $args = array() ) {
// Tooltips are only used to visually display labels.
$label = wp_strip_all_tags( $content, true );
$markup = sprintf(
'<span class="%1$s">' .
'<button type="button" class="wp-tooltip__toggle" popovertarget="%2$s" aria-label="%3$s">' .
'<span class="dashicons%4$s" aria-hidden="true"></span>' .
'</button>' .
'<span popover="hint" id="%2$s" class="wp-tooltip__bubble" role="tooltip">' .
'<span class="%1$s">
' . $button . '
<span popover="hint" id="%2$s" class="wp-tooltip__bubble" role="tooltip">' .
'<span id="%2$s-text" class="wp-tooltip__text">%5$s</span>' .
'</span>' .
'</span>',
esc_attr( $classes ),
esc_attr( $id ),
esc_attr( $args['id'] ),
esc_attr( $label ),
esc_attr( $icon ),
esc_html( $content ),
Expand All @@ -519,19 +534,17 @@ function wp_get_tooltip_helper( $content, $args = array() ) {
* attributes reproduce the accessible name and focus handling of the native element.
*/
$markup = sprintf(
'<span class="%1$s">' .
'<button type="button" class="wp-tooltip__toggle" popovertarget="%2$s" aria-label="%3$s" aria-haspopup="dialog">' .
'<span class="dashicons%4$s" aria-hidden="true"></span>' .
'</button>' .
'<span popover="auto" id="%2$s" class="wp-tooltip__bubble" role="dialog" aria-label="%3$s" tabindex="-1" autofocus>' .
'<span class="%1$s">
' . $button . '
<span popover="auto" id="%2$s" class="wp-tooltip__bubble" role="dialog" aria-label="%3$s" tabindex="-1" autofocus>' .
'<span id="%2$s-text" class="wp-tooltip__text">%5$s</span>' .
'<button type="button" class="wp-tooltip__close" popovertarget="%2$s" popovertargetaction="hide" aria-label="%6$s">' .
'<span class="dashicons dashicons-no-alt" aria-hidden="true"></span>' .
'</button>' .
'</span>' .
'</span>',
esc_attr( $classes ),
esc_attr( $id ),
esc_attr( $args['id'] ),
esc_attr( $args['label'] ),
esc_attr( $icon ),
esc_html( $content ),
Expand Down
20 changes: 19 additions & 1 deletion tests/phpunit/tests/general/wpGetTooltip.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function test_wp_get_tooltip_returns_accessible_markup() {
$tooltip = wp_get_tooltip( 'Helpful text.', array( 'id' => 'my-tip' ) );

// Toggle is a button that controls the popover and describes it.
$this->assertStringContainsString( '<button type="button" class="wp-tooltip__toggle"', $tooltip );
$this->assertStringContainsString( '<button class="wp-tooltip__toggle" popovertarget="my-tip" type="button"', $tooltip );
$this->assertStringContainsString( 'popovertarget="my-tip"', $tooltip );

// The bubble is a popover holding a text-only described element.
Expand Down Expand Up @@ -63,6 +63,24 @@ public function test_wp_get_tooltip_escapes_content() {
$this->assertStringContainsString( '&lt;script&gt;', $html );
}

/**
* Tests that custom button markup is retained, and has required attributes.
*
* @ticket 55343
*/
public function test_wp_get_tooltip_has_custom_button_markup() {
$html = wp_get_tooltip(
'Helpful text.',
array(
'button' => '<button type="button" aria-expanded="false">Contains text</button>',
)
);

$this->assertStringContainsString( 'aria-expanded="false"', $html, 'String contains attribute not supported in function.' );
$this->assertStringContainsString( 'popovertarget', $html, 'String contains required attribute added by function.' );
$this->assertStringNotContainsString( 'dashicons', $html, 'String does not contain content of default button.' );
}

/**
* Tests that the accessible labels are output and escaped in attributes.
*
Expand Down
Loading