Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c8e9abe
General: Introduce wp_get_tooltip() for accessible tooltips.
rutviksavsani Jun 18, 2026
769355f
Login and Registration: Add a tooltip to the "Remember Me" field.
rutviksavsani Jun 18, 2026
e157366
Merge branch 'trunk' into add/accessible-tooltip-remember-me
rutviksavsani Jun 18, 2026
c42b3c9
Merge branch 'trunk' into add/accessible-tooltip-remember-me
rutviksavsani Jun 22, 2026
c48a20b
Merge branch 'trunk' into add/accessible-tooltip-remember-me
rutviksavsani Jun 22, 2026
5dd6257
Merge branch 'trunk' into pr/12212
joedolson Jun 28, 2026
d2dc544
Update tooltip implementation
joedolson Jul 10, 2026
529f4e6
Update tests
joedolson Jul 13, 2026
a1f4ef1
Update wpGetTooltip.php
joedolson Jul 13, 2026
391b195
Test needs to address changed classes.
joedolson Jul 14, 2026
fff1b3c
Change default label to match icon; enforce tooltip behavior
joedolson Jul 14, 2026
877afb9
Update wp-tooltip.js
joedolson Jul 14, 2026
0cf4b5a
Now that tooltips only render the content, remove description
joedolson Jul 14, 2026
99648d3
Improve docs
joedolson Jul 14, 2026
26716f1
Update styles; restore login example for testing.
joedolson Jul 14, 2026
c8473cc
Merge branch 'trunk' into add/accessible-tooltip-remember-me
joedolson Jul 14, 2026
80d2b00
Update tests
joedolson Jul 14, 2026
f2a36e5
Remove test instance.
joedolson Jul 14, 2026
9735888
Switch to helper function with two callers
joedolson Jul 14, 2026
57720a9
Update general-template.php
joedolson Jul 14, 2026
784617f
Update tests to call appropriate functions
joedolson Jul 14, 2026
f53e5e2
Ugh
joedolson Jul 14, 2026
f7e543a
wp_get_tooltip() should not require second arg
joedolson Jul 14, 2026
bf92ddc
Sheesh.
joedolson Jul 14, 2026
5db8a20
type arg no longer required.
joedolson Jul 14, 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
1 change: 1 addition & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,7 @@ module.exports = function(grunt) {
[ WORKING_DIR + 'wp-includes/js/wp-ajax-response.js' ]: [ './src/js/_enqueues/lib/ajax-response.js' ],
[ WORKING_DIR + 'wp-includes/js/wp-api.js' ]: [ './src/js/_enqueues/wp/api.js' ],
[ WORKING_DIR + 'wp-includes/js/wp-auth-check.js' ]: [ './src/js/_enqueues/lib/auth-check.js' ],
[ WORKING_DIR + 'wp-includes/js/wp-tooltip.js' ]: [ './src/js/_enqueues/wp/wp-tooltip.js' ],
[ WORKING_DIR + 'wp-includes/js/wp-backbone.js' ]: [ './src/js/_enqueues/wp/backbone.js' ],
[ WORKING_DIR + 'wp-includes/js/wp-custom-header.js' ]: [ './src/js/_enqueues/wp/custom-header.js' ],
[ WORKING_DIR + 'wp-includes/js/wp-embed-template.js' ]: [ './src/js/_enqueues/lib/embed-template.js' ],
Expand Down
44 changes: 44 additions & 0 deletions src/js/_enqueues/wp/wp-tooltip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @output wp-admin/js/wp-tooltip.js
*/

/**
* Add focus and hover support for the 'tooltip' type in `wp_tooltip()`.
* This script can be made obsolete when support is available for Interest Invokers.
*/
(() => {

const popovers = document.querySelectorAll( '.wp-is-tooltip' );
let openTimeout;

popovers.forEach( function( popover ) {
let trigger = popover.querySelector( 'button.wp-tooltip__toggle' );
let panel = popover.querySelector( 'span.wp-tooltip__bubble' );

// Show Tooltip Function (with delay to prevent flickering).
const showTooltip = () => {
clearTimeout( openTimeout );
openTimeout = setTimeout( () => {
// Only show if it's not already open.
if ( ! panel.matches( ':popover-open' ) ) {
// pass the triggering element so implicit position anchors work.
panel.showPopover( { source: trigger } );
}
}, 300 );
};
// Hide Tooltip Function.
const hideTooltip = () => {
clearTimeout( openTimeout );
if ( panel.matches( ':popover-open' ) ) {
panel.hidePopover();
}
};

// Bind Hover and Focus Events.
trigger.addEventListener( 'mouseenter', showTooltip );
trigger.addEventListener( 'focus', showTooltip );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When it's a 'tooltip' it is still clickable, as in: hover and focus will reveal the tooltip. Pressing Enter / Spacebar or clicking it will toggle the tooltip. Maybe we should consider to prevent the click when it's a tooltip but this can be refined later after considering the desired behavior.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agree that this is best resolved later. I can see an argument for the click action still being supported. E.g., tab to focus, then hit esc to hide the popup, then want to see what the accessible name of the control is, because you've forgotten.


trigger.addEventListener( 'mouseleave', hideTooltip );
trigger.addEventListener( 'blur', hideTooltip );
});
})();
122 changes: 122 additions & 0 deletions src/wp-admin/css/wp-tooltip.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/* Accessible tooltip component. Markup from wp_get_tooltip(). */

.wp-tooltip {
display: inline-flex;
align-items: center;
vertical-align: middle;
}

/* Toggle and close buttons. */
.wp-tooltip .wp-tooltip__toggle,
.wp-tooltip .wp-tooltip__close {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
padding: 0;
border: 0;
border-radius: 2px;
background: none;
color: #50575e;
cursor: pointer;
}

.wp-tooltip .wp-tooltip__toggle:hover,
.wp-tooltip .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 .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);
}

.wp-tooltip .wp-tooltip__toggle .dashicons {
width: 20px;
height: 20px;
font-size: 20px;
}

.wp-tooltip .wp-tooltip__close .dashicons {
width: 18px;
height: 18px;
font-size: 18px;
}

/* Bubble. No position here, so it falls back to the UA's centered popover without anchoring. */
.wp-tooltip .wp-tooltip__bubble {
max-width: min(280px, calc(100vw - 32px));
margin: auto;
padding: 12px 16px;
overflow: visible;
border: 1px solid #c3c4c7;
border-radius: 4px;
background: #fff;
color: #1d2327;
font-size: 13px;
font-weight: 400;
line-height: 1.5;
text-align: left;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08);
}

.wp-tooltip.wp-is-tooltip .wp-tooltip__bubble {
background: #000;
color: #f0f0f0;
text-align: center;
padding: 4px 8px;
line-height: 1.4;
}

.wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__text {
display: block;
padding-inline-end: 24px;
}

.wp-tooltip .wp-tooltip__close {
position: absolute;
top: 6px;
inset-inline-end: 6px;
}

/* Anchor the bubble above its toggle, with a downward arrow. */
@supports (anchor-name: --a) {
.wp-tooltip .wp-tooltip__bubble {
position-area: top;
margin: 0 0 8px;
position-try-fallbacks: flip-block, flip-inline;
}

.wp-tooltip.wp-is-tooltip .wp-tooltip__bubble {
margin: 0;
}

/* Arrow: gray border (::before) under a white fill (::after). */
.wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__bubble::before,
.wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__bubble::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
width: 0;
height: 0;
border: 8px solid transparent;
border-bottom: 0;
}

.wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__bubble::before {
margin-left: -8px;
border-top-color: #c3c4c7;
}

.wp-tooltip:not(.wp-is-tooltip) .wp-tooltip__bubble::after {
margin-left: -7px;
border-width: 7px;
border-bottom: 0;
border-top-color: #fff;
}
}
162 changes: 162 additions & 0 deletions src/wp-includes/general-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,168 @@ function get_search_form( $args = array() ) {
}
}


/**
* Retrieves the markup for an accessible tooltip.
*
* Returns a button with an accessible name popover.
*
* @since 7.1.0
*
* @param string $content Plain-text tooltip content. An empty value returns an empty string.
* @param array $args {
* Optional. Arguments for building the tooltip.
*
* @type string $id Unique ID for the popover element. Default is a
* generated unique ID.
* @type string $label Not used for tooltips.
* @type string $close_label Not used for tooltips.
* @type string $icon Dashicons icon class for the toggle button.
* Default 'dashicons-editor-help'. Should match the control's
* visible label.
* @type string $class Additional class(es) for the wrapping element.
* Default empty.
* }
* @return string Tooltip HTML markup, or an empty string when no content is provided.
*/
function wp_get_tooltip( $content, $args = array() ) {
$args['type'] = 'tooltip';
return wp_get_tooltip_helper( $content, $args );
}

/**
* Retrieves the markup for an accessible tooltip or toggletip.
*
* Returns a button and either a hover/focus triggered tooltip popover or an action
* triggered toggle tip. Enqueue the `wp-tooltip` style and script where it is used.
* Tooltips are used to show the accessible name of a control.
* Toggletips are be used for longer supporting text explaining context.
*
* @since 7.1.0
*
* @param string $content Plain-text tooltip content. An empty value returns an empty string.
* @param array $args {
* Optional. Arguments for building the tooltip.
*
* @type string $id Unique ID for the popover element. Default is a
* generated unique ID.
* @type string $label Accessible label for the toggle button.
* Default 'Help', matching the default icon.
* Ignored for tooltips.
* @type string $close_label Accessible label for the close button. Default 'Close'.
* @type string $icon Dashicons icon class for the toggle button.
* Default 'dashicons-editor-help'. Should match the control's
* visible label.
* @type string $class Additional class(es) for the wrapping element.
* Default empty.
* }
* @return string Tooltip HTML markup, or an empty string when no content is provided.
*/
function wp_get_toggletip( $content, $args ) {
$args['type'] = 'toggletip';
return wp_get_tooltip_helper( $content, $args );
}
/**
* Retrieves the markup for an accessible tooltip or toggletip.
*
* Returns a button and either a hover/focus triggered tooltip popover or an action
* triggered toggle tip. Enqueue the `wp-tooltip` style and script where it is used.
* Tooltips are used to show the accessible name of a control.
* Toggletips are be used for longer supporting text explaining context.
*
* @since 7.1.0
*
* @param string $content Plain-text tooltip content. An empty value returns an empty string.
* @param array $args {
* Optional. Arguments for building the tooltip.
*
* @type string $id Unique ID for the popover element. Default is a
* generated unique ID.
* @type string $label Accessible label for the toggle button.
* Default 'Help', matching the default icon.
* Ignored for tooltips.
* @type string $close_label Accessible label for the close button. Default 'Close'.
* @type string $icon Dashicons icon class for the toggle button.
* Default 'dashicons-editor-help'. Should match the control's
* visible label.
* @type string $class Additional class(es) for the wrapping element.
* Default empty.
* @type string $type Type of tooltip: either `tooltip` or `toggletip`.
* Default 'tooltip'.
* }
* @return string Tooltip HTML markup, or an empty string when no content is provided.
*/
function wp_get_tooltip_helper( $content, $args = array() ) {
$content = trim( (string) $content );

if ( '' === $content ) {
return '';
}

$defaults = array(
'id' => '',
'label' => __( 'Help' ),
'close_label' => __( 'Close' ),
'icon' => 'dashicons-editor-help',
'class' => '',
'type' => 'tooltip',
'attributes' => 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'] : '';

if ( 'tooltip' === $args['type'] ) {
// Tooltips are only used to visually display labels.
$label = wp_strip_all_tags( $content, true );
$markup = sprintf(
'<div 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 id="%2$s-text" class="wp-tooltip__text">%5$s</span>' .
'</span>' .
'</div>',
esc_attr( $classes ),
esc_attr( $id ),
esc_attr( $label ),
esc_attr( $icon ),
esc_html( $content ),
Comment thread
joedolson marked this conversation as resolved.
);
} else {
$markup = sprintf(
'<div 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>' .
'<dialog popover="auto" id="%2$s" class="wp-tooltip__bubble" 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>' .
'</dialog>' .
'</div>',
esc_attr( $classes ),
esc_attr( $id ),
esc_attr( $args['label'] ),
esc_attr( $icon ),
esc_html( $content ),
esc_attr( $args['close_label'] ),
);
}
Comment thread
joedolson marked this conversation as resolved.

return $markup;
}

/**
* Displays the Log In/Out link.
*
Expand Down
5 changes: 4 additions & 1 deletion src/wp-includes/script-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,8 @@ function wp_default_scripts( $scripts ) {
$scripts->add( 'wp-auth-check', "/wp-includes/js/wp-auth-check$suffix.js", array( 'heartbeat' ), false, 1 );
$scripts->set_translations( 'wp-auth-check' );

$scripts->add( 'wp-tooltip', '/wp-includes/js/wp-tooltip.js', array(), false, 1 );

$scripts->add( 'wp-lists', "/wp-includes/js/wp-lists$suffix.js", array( 'wp-ajax-response', 'jquery-color' ), false, 1 );

$scripts->add( 'site-icon', '/wp-admin/js/site-icon.js', array( 'jquery' ), false, 1 );
Expand Down Expand Up @@ -1617,6 +1619,7 @@ function wp_default_styles( $styles ) {
$suffix = SCRIPT_DEBUG ? '' : '.min';

// Admin CSS.
$styles->add( 'wp-tooltip', "/wp-admin/css/wp-tooltip$suffix.css", array( 'dashicons' ) );
$styles->add( 'common', "/wp-admin/css/common$suffix.css" );
$styles->add( 'forms', "/wp-admin/css/forms$suffix.css" );
$styles->add( 'admin-menu', "/wp-admin/css/admin-menu$suffix.css" );
Expand All @@ -1636,7 +1639,7 @@ function wp_default_styles( $styles ) {

$styles->add( 'wp-admin', false, array( 'dashicons', 'common', 'forms', 'admin-menu', 'dashboard', 'list-tables', 'edit', 'revisions', 'media', 'themes', 'about', 'nav-menus', 'widgets', 'site-icon', 'l10n', 'wp-base-styles' ) );

$styles->add( 'login', "/wp-admin/css/login$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n', 'wp-base-styles' ) );
$styles->add( 'login', "/wp-admin/css/login$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n', 'wp-base-styles', 'wp-tooltip' ) );
$styles->add( 'install', "/wp-admin/css/install$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n', 'wp-base-styles' ) );
$styles->add( 'wp-color-picker', "/wp-admin/css/color-picker$suffix.css" );
$styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'imgareaselect' ) );
Expand Down
Loading
Loading