Background images in shiny



I'm new to trying to develop unique pages in shiny using css styling or html, advice or pointing in the direction of a good tutorial would be appreciated. I'd like to create a background for my home landing page that is 4 images, where each takes a corner of the background and they come together with no padding. Then an overlay with some opacity lays on top where text will sit. I haven't worked in html in many years and am confused about how to work in css into the shiny html coding.

shinyUI(navbarPage(title = 'National Park',
tabPanel(title = "Home", img(src="buffalo.png", align = 'left'),img(src="aerial.png", align = 'right' ),
img(src="sign.png", height="100%",width="100%"), img(src="stairwell.png", height="100%",width="100%"),
img(src="skylake.png", height="100%",width="100%"), img() ),
# create a text box that lays on top, with height and width of 80% of the screen. 
div {
  opacity: 0.5;

# some code I saw for first, placing in a background image, and then aligning them in the    # same row
body {
  background-image: url("header.png");
  background-position: left top;
  background-color : #000000; 

column {
  float: left;
  width: 50.00%;
  padding: 0px;

      tabPanel(title = "Home", fillPage(
        tags$style(type = "text/css",
                   ".half-fill { width: 50%; height: 400px; }",
                   ".centered { width: 75%; height: 600px; opacity: 0.5; margin-left: auto; margin-right: auto; margin-top: auto; font-family: arial; color: black; }",
                   "#one {  float: left; background-size: cover; background-image: url('buffalo.png')  }",
                   "#two {  float: right; background-size: cover; background-image: url('aerial.png') }",
                   "#three {  float: left; background-size: cover; background-image: url('sign.png')  }",
                   "#four {  float: right; background-size: cover; background-image: url('stairwell.png')  }",
                   "#overcont { float: center; background-color: #f2f3f4; <h3> blah blah blah </h3>  }"
        div(id = "one", class = "half-fill"),
        div(id = "two", class = "half-fill"),
        div(id = "three", class = "half-fill"),
        div(id = "four", class = "half-fill"),
        div(id = "overcont", class="centered"  ),
        padding = 0
      )  ) ,

".half-fill { width: 50%; height: 400px; }",
"#one {  float: left; background-size: cover; background-image: url('buffalo.png')  }",
div(id = "one", class = "half-fill"),

I've made some progress, but I'm sure there is a much better way to do this chunk of code. Plus the h3 actual text part isn't working.
Can someone also explain the ". introduction versus the "# one?


Hi -

CSS Grid and flexbox are your best friends for a 2 x2 layout with content placed on top. In terms of the structure, wrap two div elements (children) in a parent div (for demonstration, I've assigned classes. You may want to use a naming system that makes sense for you). One child element will have all of the background images and the other has the content. In shiny language, it would look like this:

	# parent
		# child: images
		tags$div(class="landing-block background-content",
			...images go here..
		# child: content (that will be placed on top of the images)
		tags$div(class="landing-block foreground-content",
			...content goes here...

The second child would have the following css properties and values assign: position:absolute, top: 0 (or some other value depending on your preference), z-index:9999. This will allow for the content to be placed over the images.

For aligning the images in a 2 x 2 layout using css grid with content placed on top, take a look at the following css grid properties: grid-template-columns, grid-row-gap, and grid-column-gap (see links below for more information; there are also many other properties that will be helpful too).

Using the example provided and adding in some css here's what it looks like.

It's easier to manage css in another file. In this example might be easier to create a file and store it in the www/ directory and read it in using

tags$head(tags$link(type="text/css", rel="stylesheet", href="path/to/css/file.css"))

The drawbacks of using this approach inside a bootstrap layouts is that some degree of overriding bootstrap styling is needed. I managed to get all of the white space eliminated, but ended up messing up the layout of the navigation bar.

Something you might want to consider is using this as a landing page written as is and placing it outside the navbarPage, and then hidding all of the other content using the shinyjs packge. Place a "get started" button in the center, which will lead the user to the first tab (data in this case). This might come in handy if you are loading many assets, large data files, and running many scripts at startup.

I've also placed this project on github. You can find the link at the bottom of this post.

Let me know if you have any questions.

Shiny app

# set image urls -- for ease, I'm calling them here
top_left <- ""
top_right <- ""
bottom_left <- ""

bottom_right <- ""

# pkgs

# ui
ui <- tagList(
    # head + css
        tags$link(href="app.css", rel="stylesheet", type="text/css")
    # UI
        # layout
        navbarPage(title = 'National Park',
                   # tab 1: landing page
                   tabPanel(title = "Home", 
                            # parent container
                                     # child element 1: images
                                     tags$div(class="landing-block background-content",
                                              # top left
                                              # top right
                                              # bottom left
                                              # bottom right
                                     # child element 2: content
                                     tags$div(class="landing-block foreground-content",
                                                       tags$p("This shiny app demonstrates
                                                     how to create a 2 x 2 layout
                                                              using css grid and
                                                              overlaying content."),
                                                       tags$p("Isn't this cool?"),
                                                       tags$p("Yes it is!")
                   # tab 2: data
                   tabPanel(title = "Data")

# server
server <- shinyServer(function(input, output){

# app
shinyApp(ui, server)


    width: 100%;
    padding: 0;
    margin: 0;

/* overriding bootstrap */
    width: 100%;
    padding: 0 !important;
    margin:0 !important;

/* layout css */
.landing-wrapper {}

.landing-wrapper .landing-block{
    width: 100%;
    height: 100vh;

.landing-wrapper .background-content{
    display: grid;
    grid-template-columns: 50% 50%;
    grid-row-gap: 0;
    grid-row-columns: 0;

.landing-wrapper .background-content img{
    display: block;
    width: 100%;

.landing-wrapper .foreground-content{
    position: absolute;
    top: 10%;
    z-index: 9999;
    display: flex;
    justify-content: center;
    align-items: center;

.landing-wrapper .foreground-content .foreground-text{
    width: 50%;
    padding: 7.5%;
    color: black;
    background-color: white;
    text-align: center;

.landing-wrapper .foreground-content .foreground-text h1{font-size: 4.5rem;}
.landing-wrapper .foreground-content .foreground-text p{font-size: 2.5rem;}

Further Reading



Github Link


Wow, thanks so much for all of this! Very informative and helpful.
Only problem is it's not working for me. I copied the css file and named it app.css and placed it in the www file and then copied the code from the ui over into my app.R (switching in my pictures, which are also located in www). It runs but it just places the pictures one on top of each other with the text showing up at the bottom. Thinking it could be an issue with other parts of my ui I just copied the www folder, app.css and app.R exactly as you have it into another folder. Still the same result. Why isn't it replicating? I am using R 3.5.1 and shiny 1.1

Last, can you explain this setup a bit more? Is landing-wrapper essentially the parent here and by calling landing-wrapper first you are associating it with that parent, and then applying the child name? I am confused at the calling of "landing-block background-content" within the child element in the ui for example.

/* layout css */
.landing-wrapper {}

.landing-wrapper .landing-block{
    width: 100%;
    height: 100vh;

.landing-wrapper .background-content{
    display: grid;
    grid-template-columns: 50% 50%;
    grid-row-gap: 0;
    grid-row-columns: 0;

Thanks for all the help!


Glad this is helpful.

Sorry to hear there are issues with the demo. A few questions: Did you run the demo by itself? Does that work for you? What about running it in your local browser? What happens then? My last guess is that everything is written correctly, but there's a missing bracket.

In the ui, I assigned each child element two classes: landing-block and *-content. The class landing-block is used for applying css to the background images and the overlayed content (in this case, just height and width). The class background-content is unique to the block with the images and the class foreground-content is unique for the content that is placed on top of the images. You can use id selectors instead (e.g, tags$div(class="landing-block", id="background-content")), but classes allow you to reuse css in other parts of the page.

In the css file, the line .landing-wrapper{} is blank to show that no styling is applied to the parent element. The reason I write css by repeating the .landing-wrapper{} at each line is to ensure that no other elements with a the same class receive the same styles. Let's take a look at the styling for h1 elements in the class foreground-content.

This can easily be written as...

h1(font-size: 4.5rem;}

However, this will apply the font-size to all h1 and p elements globally. By setting the selector path, this says:

  1. Find me the element with the class .landing-wrapper, and then
  2. Within that element, find me the element with the class .foreground-content, and then
  3. Within that element, find the the element with .foreground-text, and then
  4. Within that element, find me the element h1 and set the font-size to 4.5rem

The method I've used above is known as a descendent combinator, but really it should be written as a child combinator - parent-class > child-class - as there aren't any intermediate elements. Sometimes I'll include another wrapper around the content in the child elements. In this case, descendent combinators are a little more flexible. In a way, class selector paths are more powerful than id selectors as they can accurately find child elements, they make it easier for others to read your code (ids are harder to find in html), and classes can be reused.

Further Information

More information on css selectors

EDIT: missed a bracket in the example


So I think I found the issue. I am running my app locally in Rstudio, and the app correctly loads the pictures located in the www folder, but it does not see, or is not loading, app.css. When I inspect the run app I am seeing that it is not loading the app.css and using some defaults and thus your great css code isn't being used. See picture for the addresses being loaded.

Why is it not seeing the app.css in the www folder?


This is puzzling. Are there any errors in the console? (last menu item in first screenshot)

May I ask how your project directory is structured? The width of the black bar suggests that you might be using an absolute path for reading the css file (files must be loaded using relative paths). Are all the files in the same directory? (see example below)

# example project
        - ui.R
        - server.R
        - www/
             - app.css
             - app.js

When the app launches, the "home" directory is set to inside the www/ folder and the css file can be read in as href = "app.css". If you created a subdirectory in www and the placed a css file in the subdirectory, the path would be href = "subdir/app.css".

Hope that helps!

Further Infomartion

You can find more information on shiny and custom css files here: