32

I'm working with a CMS that prevents us from editing the head section. I need to add css stylesheet to the site, right after the tag. Is there a way to do this with JS, where I can add a script to the bottom of the page (I have access to add script right before the tag) that would then inject the stylesheet into the head section?

Rahil Pirani
  • 571
  • 2
  • 8
  • 14

5 Answers5

116

Update: According to specs, the link element is not allowed in the body. However, most browsers will still render it just fine. So, to answer the questions in the comments - one really has to add link to the head of the page and not the body.

function addCss(fileName) {

  var head = document.head;
  var link = document.createElement("link");

  link.type = "text/css";
  link.rel = "stylesheet";
  link.href = fileName;

  head.appendChild(link);
}

addCss('{my-url}');

Or a little bit easier with jquery

function addCss(fileName) {
   var link = $("<link />",{
     rel: "stylesheet",
     type: "text/css",
     href: fileName
   })
   $('head').append(link);
}

addCss("{my-url}");

Original answer:

You don't need necessarily add it to the head, just add it to the end of body tag.

$('body').append('<link rel="stylesheet" type="text/css" href="{url}">')

as Juan Mendes mentioned, you can insert stylesheet to the head instead

$('head').append('<link rel="stylesheet" type="text/css" href="{url}">')

And the same without jQuery (see code above)

John R Perry
  • 3,916
  • 2
  • 38
  • 62
vittore
  • 17,449
  • 6
  • 44
  • 82
  • 3
    You could use almost the same thing to append it to the head. – Ruan Mendes Aug 06 '12 at 18:25
  • @Juan Mendes, I know, but what I'm sayign is that it just can be inserted in the body – vittore Aug 06 '12 at 19:17
  • Hi vittore, I've added the second script you mentioned right before

    tag, however, it doesn't seem to load the CSS within the head section... thoughts? The main problem, is we are creating a new stylesheet to overwrite the the stylesheet that the CMS forces us to use. So we created a NEW stylesheet to overwrite their settings. We woud like it to load RIGHT after they load theirs... otherwise, their stylesheet loads, and shows the site, and then a few seconds later, our changes show up...

    – Rahil Pirani Aug 06 '12 at 19:39
  • Does anyone know, is there different between this two variant (dynamic css in head vs in body), as best practice or performance? – Rudolf Manusachi May 19 '16 at 19:08
  • I have attempted the method of appending the link element to the head and found that it works, however, this causes a very noticeable flash in the browser since everything is rerendered. I observed this on Google Chrome, but had users in all different browsers reporting it. I attempted this in two different products with the same result, and they both had very different HTML, indicating that the flash will occur regardless of what HTML is present. Long story short, I do not recommend adding the link to the head. – Trevor Oct 11 '17 at 16:56
  • @vittore according to me pure javascript is better as we can add the function in the head and call it in the head so when the page loads it shows proper to the user. – webdevanuj Mar 06 '21 at 07:01
22

This will do what you want in an intelligent way. Also using pure JS.

function loadStyle(href, callback){
    // avoid duplicates
    for(var i = 0; i < document.styleSheets.length; i++){
        if(document.styleSheets[i].href == href){
            return;
        }
    }
    var head  = document.getElementsByTagName('head')[0];
    var link  = document.createElement('link');
    link.rel  = 'stylesheet';
    link.type = 'text/css';
    link.href = href;
    if (callback) { link.onload = function() { callback() } }
    head.appendChild(link);
}
Eddie
  • 1,428
  • 14
  • 24
  • 2
    I've edited to add missing `i` in the for loop: `if(document.styleSheets[i].href == href){` – cronoklee Mar 08 '16 at 11:47
  • I have attempted the method of appending the link element to the head and found that it works, however, this causes a very noticeable flash in the browser since everything is rerendered. I observed this on Google Chrome, but had users in all different browsers reporting it. I attempted this in two different products with the same result, and they both had very different HTML, indicating that the flash will occur regardless of what HTML is present. Long story short, I do not recommend adding the link to the head – Trevor Oct 11 '17 at 16:58
  • The solution is to control the rendering through JS as well. Like load the css before, what ever it is used for loads. You can add a callback argument and put an onload event inside the function. This will tell you when it is ready to be used. Same goes for other elements like frames, scripts, ect. – Eddie Dec 20 '17 at 22:12
  • upvote for considering the use case, where multiple style sheets could potientially be added. I used index of for my particular scenario for it to work, also had to check for null as for some reason one of the hrefs was null. ```if (document.styleSheets[i].href != null && document.styleSheets[i].href.indexOf(href) != -1)``` – chris c Nov 21 '19 at 05:57
6

I've modified Eddie's function to remove or toggle the stylesheet on or off. It will also return the current state of the stylesheet. This is useful for example, if you want to have a toggle button on your website for vision-impaired users and need to save their preference in a cookie.

function toggleStylesheet( href, onoff ){
    var existingNode=0 //get existing stylesheet node if it already exists:
    for(var i = 0; i < document.styleSheets.length; i++){
        if( document.styleSheets[i].href && document.styleSheets[i].href.indexOf(href)>-1 ) existingNode = document.styleSheets[i].ownerNode
    }
    if(onoff == undefined) onoff = !existingNode //toggle on or off if undefined
    if(onoff){ //TURN ON:
        if(existingNode) return onoff //already exists so cancel now
        var link  = document.createElement('link');
        link.rel  = 'stylesheet';
        link.type = 'text/css';
        link.href = href;
        document.getElementsByTagName('head')[0].appendChild(link);
    }else{ //TURN OFF:
        if(existingNode) existingNode.parentNode.removeChild(existingNode)
    }
    return onoff
}

Sample usage:

toggleStylesheet('myStyle.css') //toggle myStyle.css on or off

toggleStylesheet('myStyle.css',1) //add myStyle.css

toggleStylesheet('myStyle.css',0) //remove myStyle.css
cronoklee
  • 6,482
  • 9
  • 52
  • 80
  • 2
    How about using the standard ``.disabled`` property on a StyleSheet to turn it on/off – Mr Allrood Feb 09 '17 at 10:54
  • Obviously that's an option and would be a little faster to turn on the second time, but this would require a third case in the function to handle the stylesheet already existing in a disabled state, so the function would be a little longer. – cronoklee Mar 14 '17 at 16:00
4

You can use pure javascript and still elegance in the modern browser.

const range = document.createRange()
const frag = range.createContextualFragment(`THE CONTENT IS THE SAME AS THE HTML.`)
document.querySelector("YOUR-NODE").append(frag) 

It's very easy to add any HTML code.

Document

Example 1

Add the style on the head by javascript.

<head></head><body><button class="hover-danger">Hello World</button></body>
<script>
  const range = document.createRange()
  const frag = range.createContextualFragment(`
<style>
  .hover-danger:hover{
    background-color: red;
    font-weight: 900
  }
</style>
`
)
  document.querySelector("head").append(frag)  
</script>

Example 2

Import CSS, JS, and modify the existing stylesheet.

<head></head>
<body><button class="btn btn-primary hover-danger">Hello world</button></body>

<script>
  const range = document.createRange()
  const frag = range.createContextualFragment(`
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"/>
`)
  document.querySelector("head").append(frag)

  window.onload = () => {
    //  If you don't want to import the new source, you can consider adding the data to exists source.
    const nodeLink = document.querySelector(`link[href^="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"]`) // ^: match begin with my input
    if (nodeLink)  { // !== null
      const stylesheet = nodeLink.sheet
      const myCSS = `
background-color:red;
font-weight: 900;
`
      stylesheet.insertRule(`.hover-danger:hover{ ${myCSS} }`, stylesheet.cssRules.length)
    }
  }
</script>

You must have permission to modify the CSS directly

If you get the error:

Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules,

then you can reference: https://stackoverflow.com/a/49994161/9935654

Carson
  • 6,105
  • 2
  • 37
  • 45
2

Here is a simple one-liner to add a stylesheet:

document.head.insertAdjacentHTML('beforeend', `<link typs="text/css" rel="stylesheet" href="<Source URL>">`);
H K
  • 1,062
  • 8
  • 10