1

The theme I'm using makes extensive use of internal stylesheets to style various types of pages and their elements. For example, through their plugin UI, I can configure a specific page's hero layout and content... including font, size and color for various text elements. After configuring the hero and publishing the page, an internal stylesheet is added to the page's <head> tag:

<style id="ut-hero-custom-css" type="text/css">
    #ut-hero .hero-inner {
        text-align: right
    }
    .hero-description {
        color: #000000;
    }
    .hero-description {
        background: #FCB54B;
        padding-bottom: 0;
        margin-bottom: 5px;
    }
    #ut-hero .hero-title {
        color: #1777FF;
    }
</style>

Unfortunately, the plugin is limited in what it allows me to customize so I need to add some of my own CSS to fine tune things. Let's say I want to display a border around the block of text with class="hero-description". I'd need to add

.hero-description {
    border: 1px solid #c00;
} 

and have it be applied to the page AFTER the initial declaration above. Adding it my child-theme's style.css file or any other CSS file I register and enqueue adds it BEFORE. I was hoping that I could specify the inline stylesheet embedded by the plugin as a dependency for my new CSS file when enqueueing it, but I don't see any handle or reference to it in $wp_styles so I couldn't do that. Can you even register an internal stylesheet?

Anyway, this can be broken down to a very general problem. I want to have the "last word" on the page's CSS (excluding inline styles and scoped elements). Is there not a direct way to specify that a line of code such as:

<link rel="stylesheet" src="mycss.css">

always be included right before the closing </head> tag?

1
  • You could just make the rule more specific, then it'd take precedence over the other rule, no matter which order it was loaded, even if it appeared first. You shouldn't need to rely on the ordering of CSS loading to style things right for things as specific as hero images
    – Tom J Nowell
    Commented Dec 20, 2017 at 15:07

4 Answers 4

1

It's not elegant but it works.

Add these lines to functions.php:

ob_start();

add_action('shutdown', function() {
    $final = '';
    $levels = ob_get_level();
    for ($i = 0; $i < $levels; $i++) {
        $final .= ob_get_clean();
    }

    // append styles just before </head>
    $final = str_replace( "</head>", '<link rel="stylesheet" src="mycss.css"></head>', $final );

    echo $final;
}, 0);

Alternatively you can move all the inline styles at the beginning of the head, better if just after the title tag:

ob_start();

add_action('shutdown', function() {
    $final = '';
    $levels = ob_get_level();
    for ($i = 0; $i < $levels; $i++) {
        $final .= ob_get_clean();
    }

    /* Adjust the final output */

    // load HTML DOM
    $dom= new DOMDocument(); 
    $dom->preserveWhiteSpace = false;
    $dom->formatOutput       = true;
    $dom->loadHTML( '<?xml encoding="utf-8" ?>' . $final );

    // collect styles and remove them from DOM
    $inline_styles = "";
    $styles = $dom->getElementsByTagName("style");
    foreach( $styles as $style )
    {
        $inline_styles .= $dom->saveHTML( $style );
        $style->parentNode->removeChild( $style );
    }

    $final = $dom->saveHTML();

    // strip utf-8 workaround
    $final = str_replace( '<?xml encoding="utf-8" ?>', "", $final );

    // append styles after <title>
    $final = str_replace( "</title>", "</title>\n" . $inline_styles, $final );

    echo $final;
}, 0);
4
  • Why ob_start();? That doesn't look useful in this context.
    – fuxia
    Commented Dec 19, 2017 at 23:05
  • ob_start(); collect the HTML ouput buffer from the beginning, in order to parse it inside the shutdown function then print the parsed output instead of the standard one.
    – Kalamun
    Commented Jan 4, 2018 at 12:05
  • That is pointless here, and it's usually very slow and dangerous.
    – fuxia
    Commented Jan 4, 2018 at 12:25
  • Is not pointless: adding it on functions.php means executing it at the very beginning of WordPress initialization, before any output. "Very slow" is a funny opinion, considering the amount of operations executed by WP this one will probably be one of the lightest. About "dangerous": yes, I better defined it as "not elegant" (I can't figure out any security issue) but anyway, it's the only work-around that satisfy the @Daveh0 request. And works. And will work until there will be a </head> tag, with no compatibily problems.
    – Kalamun
    Commented Jan 4, 2018 at 16:07
0

You would add dependencies to your wp_enqueue_style() function calls.

function my_enqueues() {
    wp_enqueue_style( 'style-one', '{path}' );
    wp_enqueue_style( 'style-two', '{path}', array( 'style-one' ) );
    wp_enqueue_style( 'style-three', '{path}', array( 'style-two' ) );
    wp_enqueue_style( 'style-final', '{path}', array( 'style-three' ) );
}
add_action( 'wp_enqueue_scripts', 'my_enqueues' );

What this code does is make each style sheet dependent on the previous one. So they will load in the order style-one, style-two, etc.

Of course one of the {path} placeholders should be your style.css file called with get_stylesheet_uri();.

If you want to load one after a plugin has loaded one that overrides, then try using the plugins_loaded hook:

function my_plugin_overrides() {
   wp_deregister_style( 'plugin-styles' ); // OPTIONAL
   wp_enqueue_style( 'plugin-css-overrides', '{path}', array( 'deps' ) );
}
add_action( 'plugins_loaded', 'my_plugin_overrides' );
13
  • I get how to add dependencies using wp_enqueue_styles. My problem is that the theme/plugin author is writing internal stylesheets to the <head> tag of the page. Any external stylesheets I add by hooking into wp_enqueue_scripts will still be added before the author's internal stylesheets and therefore useless for this particular scenario.
    – Daveh0
    Commented Jun 25, 2017 at 22:24
  • Try adding a high number after the function name in add_action(). That way WordPress will execute this later in the chain.
    – Cedon
    Commented Jun 25, 2017 at 22:52
  • Hackish way to get around it (if you don't wanna use wp_enqueue_scripts with PHP_INT_MAX is to use wp_head action, it will always run after that and thus have your "scripts" later.
    – jimihenrik
    Commented Jun 26, 2017 at 3:32
  • @jimihenrik - so you're saying that if I call add_action() with wp_head instead of wp_enqueue_scripts, my <link rel="stylesheet"... > will be printed out closer to the end of <head>? What potential complications could this bring on?
    – Daveh0
    Commented Jun 26, 2017 at 4:15
  • Yes that's exactly what I'm saying, and I can't come up with any complications on the spot. There probably could be some but I don't know what. But yeah probably wp_enqueue_scripts with stupidly big number (like phpmaxint) should do the job anyway.
    – jimihenrik
    Commented Jun 26, 2017 at 10:11
0

Since WP 4.7 the WP dashboard includes a CSS customizer (Appearence->Cusomize->Additional CSS). This might be a solution for your theme specific CSS as WP adds the style block to <head> after enqueued CSS and possibly other additions by theme.

Normal practice as per Cedons enqueing answer or jimihenrik's comment should work.

However; it appears this theme needs a "non-standard" solution (I like your JS solution) - alternatives include an edited copy of header.php in child theme.

"an internal stylesheet is added to the page's <head>"

The theme could be adding in a variety of ways in header.php e.g. hard coding; via a theme "action hook"; via wp-head

The last part of header.php typically contains:

<?php possibly_themes_own_action_hook(); ?>
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
  Some HTML for header and navigation & other stuff prior to Loop adding say H1 title

If theme does not have header.php or it is missing </head> then relevant code is probably in templates.

In your child header.php just above </head> (if not present see below) you could either:

  • hard code your CSS or stylesheet link above </head>
  • or <php include('path/to/mycss.css');?>

  • or add your own action:

     my_add_to_head(); 
    

    and in your functions.php

     function my_head_function() {
     ?>
     <style>.hero-description {border:1px solid #c00;}</style> 
     <?php
     // or stylesheet link or file_get_contents to get css file and echo
     // + any other stuff you want to add to head
     }
     add_action('my_add_to_head', 'my_head_function');
    

Solutions are bad practice especially if someone else ends up having to maintain but may be the best option with your theme.

"when i used wp_head, it ended up in the tag with all the JavaScript files."

If you mean within the body tag then there is a coding error in the theme. Otherwise it is "non-standard" design and could cause many plugins to "fail". Either wp_head(); is after the body tag or "wp_head" is used to insert <body> and maybe </head>.

If </head> is not in header.php or templates then adding the above "solutions" just above wp_head(); might still work but plugin issues will remain.

-1

I am completely blocked here. SO, for the time being I will be using the following "workaround" so that I can at least continue development:

$(function() {
    $('head').append('
      <link rel="stylesheet" type="text/css" src="mycss.css">
    ');
});

Definitely not the "right" way, but I should be able to easily port my new, overriding styles (as referenced in mycss.css above) to wherever they belong when done the "right" way when I get it figured out.

1
  • For future readers, this is NOT the right way (as the OP says).
    – JakeParis
    Commented Aug 16, 2023 at 15:30

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.