0

I know there is a body_class function for WordPress. But is there one (or a way) to add a class to the HTML element?

My goal is to be able to add a unique class (or ID) to a page's HTML element. Currently my theme is adding the page-id-XXXX class to the body element, but I need a way to have a unique class or ID on the actual HTML element. I'd probably be fine having the page-id-XXXX also added onto the HTML element, although I'd prefer being able to have a custom field that's added to each page, where I can type in the class/ID that would then get added to the HTML element.

At the very least, is there a function I can use to add a class or ID to the HTML element, similar to how the body_class function works?

4
  • Would you mind explaining why you need to do this? (I'm just curious.) Also, is a JavaScript solution acceptable, or would you prefer a PHP approach? Commented May 27, 2017 at 23:52
  • 1
    @DaveRomsey my main reason for this is I'm creating a custom one-off page on an existing WordPress website. It's a page that will be used for a 4K TV Slideshow. The current theme sets CSS of font-size and line-height on the <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).
    – Garconis
    Commented May 28, 2017 at 1:27
  • I would definitely prefer PHP over JS. For now I'm just using internal CSS to change the HTML element sizes, but would prefer to add the CSS to the main external CSS file... hence the desire to be able to target this specific page's HTML element.
    – Garconis
    Commented May 28, 2017 at 1:30
  • Check this answer it solved my problem. Commented Sep 5, 2017 at 7:57

5 Answers 5

3

Why not just add the class directly to the tag?

<html <?php language_attributes(); ?> class="page-id-<?php the_ID(); ?>">
1
  • 1
    I'm trying to avoid overriding theme files.
    – Garconis
    Commented Oct 10, 2019 at 1:04
2

This seemed to work just fine for me

add_filter( 'language_attributes', 'add_no_js_class_to_html_tag', 10, 2 );

function add_no_js_class_to_html_tag( $output, $doctype ) {
    if ( 'html' !== $doctype ) {
        return $output;
    }

    $output .= ' class="no-js"';

    return $output;
}

I couldn't find anything in the codex for it. Credit to https://gist.github.com/nickdavis/73d91d674b843b77a1cd0a21f9c0353a

3
  • That worked to add a custom class? It doesn't do anything for me. If I set it to be a different attribute instead of class, then it works. For example, this works: function add_class_to_html_element( $output ) { $output .= ' attribute="value"'; return $output; } add_filter( 'language_attributes', 'add_class_to_html_element' ); ... but as soon as you change attribute to class, it doesn't show up. Looks like id will work though.
    – Garconis
    Commented Dec 11, 2020 at 14:30
  • I'm using ClassicPress 1.2, which forked at WP 4.9 but it's working for me. <html lang="en-US" class="no-focus-outline"> is what my head is looking like now.
    – James0r
    Commented Dec 11, 2020 at 19:03
  • Ah, mine is already doing a no-js or js class from the theme, so maybe if it already has a class, it can't add another?
    – Garconis
    Commented Dec 11, 2020 at 23:20
2

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;
    }
1
  • I am considering temporary code to modify all header tags to get around a WP v6 bug - and a search led me here. I thank you kindly for this code that would do exactly what I have been considering. Considering performance however, I especially thank you for helping me to recognize that this is exactly what I do not want to do in a production site. Yes, this is a great solution, but I think only as a last resort. I hope this comment helps someone who is tempted to simply copy/paste.
    – TonyG
    Commented Oct 13, 2023 at 20:56
1

Since you're outside of the loop the cleanest solution is to simply create your own function like so

function wpse_268339_get_post_class() {
    global $post;
    if ( ! empty( $post->ID ) ) {
        return 'post-' . $post->ID;
    }
}

Then, in your header.php template just call the function as escape the output in a body class. This example assumes you already have a couple of classes on the <html> tag.

<html <?php language_attributes(); ?> class="no-js no-svg <?php echo esc_attr( wpse_268339_get_post_class() ); ?>">

This will only output a class name if you're on a Post / Page.

Alternatively, if all of the normal body classes don't harm anything, you can just use body_class() on the <html> tag as well. The function works anywhere.

<html <?php body_class(); ?>>
1
  • I'd prefer to not modify the actual PHP file. The theme does currently have classes (not unique ones) on the HTML element. I'd also prefer to not add the body classes to the HTML element, as I'm sure the theme doesn't expect that.
    – Garconis
    Commented May 28, 2017 at 1:33
0

Thanks to the help of this answer, I've found this solution works for adding an id. Though I'm not sure about adding to the class attribute.

function add_id_to_html_element( $output ) {
    global $post;
    $output .= ' id="custom-id-' . $post->ID . '"';
    return $output;
}
add_filter( 'language_attributes', 'add_id_to_html_element' );

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.