WordPress does not have an equivalent of body_class()
for the html tag.
Here's an approach that uses output buffering to capture the final output of the HTML document being rendered. The output buffering code below was adapted from solutions posted here and here. This allows us to access the final output of the HTML document which we then parse and edit and is done without needing to modify the theme's template files.
First, we start output buffering and wire up our shutdown
function, which
works by iterating through all the open buffer levels, closing them and capturing their output. It then fires off the wpse_final_output
filter, echoing the filtered content.
/**
* Start output buffering
*/
add_action( 'wp', 'wpse_ob_start' );
function wpse_ob_start() {
// Bail immediately if this is the admin area.
if ( is_admin() ) {
return;
}
// Bail immediately if this is a feed.
if ( is_feed() ) {
return;
}
// Start output buffering.
ob_start();
add_action( 'shutdown', 'wpse_ob_clean', 0 );
}
/**
* Ensure the buffer is clean and then trigger the wpse_final_output filter.
* This fires right before WP's similar shutdown functionality.
*/
function wpse_ob_clean() {
$final = '';
// We'll need to get the number of ob levels we're in, so that we can
// iterate over each, collecting that buffer's output into the final output.
$levels = ob_get_level();
for ( $i = 0; $i < $levels; $i++ ) {
$final .= ob_get_clean();
}
// Apply any filters to the final output
echo apply_filters( 'wpse_final_output', $final );
}
Here the HTML is parsed and the custom filter wpse_additional_html_classes
is triggered which lets us add our additional classes in a separate function. This code is a bit verbose, but it covers several edge cases that I've run into when using DOMDocument
to parse HTML.
add_filter( 'wpse_final_output', 'wpse_html_tag', 10, 1 );
/**
* Parse final buffer output. Triggers wpse_additional_html_classes, which
* allows us to add classes to the html element.
*/
function wpse_html_tag( $output ) {
// Filterable list of html classes.
$additional_html_classes = apply_filters( 'wpse_additional_html_classes', array() );
// Bail if there are no classes to add since we won't need to do anything.
if ( ! $additional_html_classes ) {
return $output;
}
// Create an instance of DOMDocument.
$dom = new \DOMDocument();
// Suppress errors due to malformed HTML.
// See http://stackoverflow.com/a/17559716/3059883
$libxml_previous_state = libxml_use_internal_errors( true );
// Populate $dom with buffer, making sure to handle UTF-8, otherwise
// problems will occur with UTF-8 characters.
// Also, make sure that the doctype and HTML tags are not added to our HTML fragment. http://stackoverflow.com/a/22490902/3059883
$dom->loadHTML( mb_convert_encoding( $output, 'HTML-ENTITIES', 'UTF-8' ), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
// Restore previous state of libxml_use_internal_errors() now that we're done.
// Again, see http://stackoverflow.com/a/17559716/3059883
libxml_use_internal_errors( $libxml_previous_state );
// Create an instance of DOMXpath.
$xpath = new \DOMXpath( $dom );
// Get the first html element.
$html = $xpath->query( "/descendant::html[1]" );
// Get existing classes for html element.
$definedClasses = explode( ' ', $dom->documentElement->getAttribute( 'class' ) );
// Adds our additional classes to the existing classes. Ensure that proper spacing of class names
// is used and that duplicate class names are not added.
foreach ( $html as $html_tag ) {
$spacer = ' ';
// Spacer will be set to an empty string if there are no existing classes.
if ( isset( $definedClasses[0] ) && false == $definedClasses[0] ) {
$spacer = '';
}
foreach ( $additional_html_classes as $additional_html_class ) {
if ( ! in_array( $additional_html_class , $definedClasses ) ) {
$html_tag->setAttribute(
'class', $html_tag->getAttribute( 'class' ) . $spacer . $additional_html_class
);
}
$spacer = ' ';
}
}
// Save the updated HTML.
$output = $dom->saveHTML();
return $output;
}
The wpse_additional_html_classes
filter allows us to easily filter the additional classes added the HTML element. In the example below, a special class is added for the post id (of course there are many cases where there will be no post id). An array of custom class names is also added. Customize the classes/logic for adding classes to suit your needs, then return the array of class names.
add_filter( 'wpse_additional_html_classes', 'wpse_add_additional_html_classes', 10, 1 );
/**
* Filters list of class names to be added to HTML element.
*
* @param array $classes
* @return array
*/
function wpse_add_additional_html_classes( $classes ) {
// Example of adding a post ID class.
if ( is_singular() ) {
$post_id = get_the_ID();
if ( $post_id ) {
$post_id_class = "post-id-{$post_id}";
if ( ! in_array( $post_id_class, $classes ) ) {
$classes[] = $post_id_class;
}
}
}
// Add some more classes.
$additional_classes = [
'class-1',
'class-2',
'class-3',
];
$classes = array_merge( $classes, $additional_classes );
return $classes;
}
<html>
element. Additionally, some of the theme's content styling uses rem for the font size. Essentially, I'd like to drastically increase the font-size and line-height of the HTML element — for this page only — so that I can continue using rem sizing (since rem works from the HTML sizes).