Display Paragraphs Edge-to-edge using Bootstrap in Drupal 8

Want to learn more about Paragraphs? Then check out our online video course called “Build Edge-to-edge Sites using Paragraphs in Drupal 8“.

So far I’ve introduced you to the Paragraphs module where we created a basic paragraph type which allows you to display content as movable components. Then we looked at how to create container paragraphs, and this time we created a Banner which let us display nested paragraph items.

By now you should have a basic understanding of why you should use Paragraphs and how to use the module.

Today I want to teach you how to display paragraphs edge-to-edge using Bootstrap as the theme. We’ll use the Banner paragraph, which we created in the last tutorial, to display an image full width but have the nested paragraphs centered.

Below is an image of the end result:

Fig 1.2

It’s All About the Containers

We’ll use Bootstrap to streamline the build but we’ll need to change how the container class is used in the page template. For people who don’t know, the container is used to wrap the site content and “contain” any grids within it. In other words, it simply centers the content. Any content outside of the container goes edge-to-edge.

So to style everything, we’ll need the Banner paragraph type to be outside of the container and then have the container on any nested paragraph items.

This is where most of the complexity lays in building this type of site. Controlling where the container gets added or removed can be tricky in Drupal.

For simplicity, we’ll assume all paragraphs (except nested ones) will be displayed at full width. Things start to get complicated when you want to display a paragraph edge-to-edge as well as in a sidebar. So for simplicity all paragraphs will go edge-to-edge.

Getting Started

Make sure you have Paragraphs and Entity Reference Revisions installed and download Bootstrap.

Below are the Drush and Drupal Console commands to download and install:


$ drush dl paragraphs entity_reference_revisions bootstrap
$ drush en paragraphs

Drupal Console:

$ drupal module:download paragraphs 8.x-1.0-rc4
$ drupal module:download entity_reference_revisions 8.x-1.0-rc4
$ drupal module:download bootstrap 8.x-3.0-beta2
$ drupal module:install paragraphs

Step 1: Create the Content and Banner Paragraph Type

For this tutorial, you’ll need the Content and Banner paragraph type which we created in the last two tutorials. You can learn how to create the Content paragraph type by reading “Build Edge-to-edge Sites using Paragraphs Module in Drupal 8“. And for the Banner, read “How to Create Powerful Container Paragraphs in Drupal 8“.

Make sure you’ve created these two paragraph types before continuing.

Implement the Banner Preprocess Hook

The Banner paragraph needs a bit of code in its preprocess, don’t forget to implement it or the banner won’t work.

function HOOK_preprocess_paragraph__banner(&$variables) {
  $paragraph = $variables['paragraph'];
  if (!$paragraph->field_image->isEmpty()) {
    $image = $paragraph->field_image->entity->url();
    $variables['attributes']['style'][] = 'background-image: url("' . $image . '");';
    $variables['attributes']['style'][] = 'background-size: cover;';
    $variables['attributes']['style'][] = 'background-position: center center;';

Step 2: Configure Bootstrap Theme for Drupal 8

As mentioned in the introduction, we’ll use Bootstrap for the front-end. So go ahead and make sure it’s been downloaded and create a sub-theme. We’ll need to override a few templates so it’s preferable to create your own sub-theme.

Refer to the Bootstrap theme documentation to learn how to create a sub-theme.

Step 3: Modify the Page Template (page.html.twig)

Now that we’ve configured the paragraph types and our Bootstrap theme, let’s make the Page content type edge-to-edge.

As mentioned earlier, the container is used to wrap the site content. The problem now is the page.html.twig, which wraps the content types and paragraphs, implements a container. For this to work, we’ll need to remove the .container class from the page.html.twig.

Now, to make the site some what useful, we only want to remove the container if we’re on a node or content page. We’ll need to create a file called page--node.html.twig which’ll be used if you’re on an article or page.

On a proper site, you may only want a single content type to go edge-to-edge. Often I create a “Landing page” content type which’ll be used to create fluid pages. But an article may have a sidebar so it shouldn’t go edge-to-edge.

To handle this type of logic, a fair bit of custom code is required. For this tutorial, we’ll keep things simple and just assume all content will be edge-to-edge.

Go into your Bootstrap theme and create a file called page--node.html.twig and replace the {% block main %} with the following:

Fig 1.0

{# Main #}
{% block main %}
  <div role="main" class="main-container js-quickedit-main-content">
    <div class="container">
      <div class="row">
        {# Header #}
        {% if page.header %}
          {% block header %}
            <div class="col-sm-12" role="heading">
              {{ page.header }}
          {% endblock %}
        {% endif %}
          {# Highlighted #}
          {% if page.highlighted %}
            {% block highlighted %}
              <div class="highlighted">{{ page.highlighted }}</div>
            {% endblock %}
          {% endif %}
          {# Breadcrumbs #}
          {% if breadcrumb %}
            {% block breadcrumb %}
              {{ breadcrumb }}
            {% endblock %}
          {% endif %}
          {# Action Links #}
          {% if action_links %}
            {% block action_links %}
              <ul class="action-links">{{ action_links }}</ul>
            {% endblock %}
          {% endif %}
          {# Help #}
          {% if page.help %}
            {% block help %}
              {{ page.help }}
            {% endblock %}
          {% endif %}
    {# Content #}
      {# Content #}
      {% block content %}
        <a id="main-content"></a>
        {{ page.content }}
      {% endblock %}
{% endblock %}

Click here to view the full updated version of page--node.html.twig

In the above code, all we’ve done is move a few DIVs around and made sure the content region has no container.

Once you’ve created the twig file, don’t forget to rebuild the cache.

Step 4: Add Container to Content Paragraph Type

Now we need to add the .container class to the Content paragraph type. This means that when it’s displayed the text will be centered, while the banner will be displayed edge-to-edge.

1. Find the paragraph.html.twig and copy it into the theme and rename it to paragraph--content.html.twig.

2. Replace the code in the file with the following:

set classes = [
'paragraph--type--' ~ paragraph.bundle|clean_class,
view_mode ? 'paragraph--view-mode--' ~ view_mode|clean_class,
<div{{ attributes.addClass(classes) }}>
 {{ content }}

Click here to view the full updated version of paragraph--content.html.twig

All we’ve done is add the container class to the classes variable.

3. Don’t forget to rebuild the cache.

Step 5: Test Banner Paragraph

Go ahead and create a banner paragraph with a few nested content paragraphs. Once created it should look something like the image below:

Fig 1.2


I’ll admit, this is not the easiest thing to build. There’s a lot of moving parts and depending on your requirement it can be built in a few different ways. But it all comes down to who controls the container. Just remember, you must move the responsibility of the container from the page template to the paragraph.


Q: I made changes in a template and those changes are not appearing.

Did you rebuild the cache (clear site cache in Drupal 7)? Every time you override a template you must rebuild the cache, same as in Drupal 7.

Ivan Zugec

About Ivan Zugec

Ivan is the founder of Web Wash and spends most of his time consulting and writing about Drupal. He's been working with Drupal for 10 years and has successfully completed several large Drupal projects in Australia.

16 thoughts on “Display Paragraphs Edge-to-edge using Bootstrap in Drupal 8”

  1. I’ve seen a similar approach used in D7 sites before, but one of the problems we end up with is that non-paragraph based pages loose their container (e.g. /user/login). Have you considered making the container in the page template conditional on content type or something like that?

  2. Hi Dan,

    Yes, I would put some logic in the page.html.twig to conditionally add the container.

    This is how I’ve done it in the past.

    **First**, figure out what constitutes an edge-to-edge page. Is it content type? Or it could be a particular Display Suite layout. Or even a custom view mode.

    On a few past projects, I’ve removed the container if the content type is controlled by Panels and let the Panels layout define a container.

    But it really depends how you built your site.

    **Second**. In this hook, `hook_preprocess_page`, you write your code to check if the node is edge-to-edge. Again what constitutes an edge-to-edge page. Then define a variable like `is_fluid` or `is_edgy` and return TRUE if it’s edge-to-edge.


    Add some basic conditional code in `page.html.twig`. like the code below:

    {% set dynamic_container = is_fluid ? 'leadgen-fluid' : 'container' %}

    **LeadGen Distro**

    I’ve been working on a Drupal 8 Paragraph powered distro called [LeadGen](https://www.drupal.org/project/leadgen). It implements a lot of this edge-to-edge stuff.

    In the distro, I have a function called [leadgen_layouts_is_fluid](http://cgit.drupalcode.org/leadgen/tree/modules/leadgen/leadgen_layouts/leadgen_layouts.module#n72) which checks if the layout is powered by Display Suite and a specific category within Display Suite.

    Then in [page.html.twig](http://cgit.drupalcode.org/leadgen/tree/themes/leadgen/leadgen_basic/templates/page.html.twig), I have a `dynamic_container` variable which sets the container if `is_fluid` returns TRUE.

    As you can see there’s a lot of moving parts. 🙂

    Hope this helps.


    1. Nice, thanks for the quick reply!
      I’d probably preference your second option there (in fact I think that’s how we handle it in our D7 sites). Mostly I’m just glad to see people using Paragraphs in D8 🙂

  3. Hi Ivan,

    I’ve downloaded and installed Paragraphs, Entity Reference Revisions and have a Bootstrap SUB Theme. I’ve also completed your node/259 and /260 tutorials and implemented the preprocess HOOK. Thanks for them, they are awesome!

    Things get iffy when I add a paragraph field to a basic page. I loose the ability to add the banner and the page–node.html.twig doesn’t seem to work. Is there any chance my container class could be located somewhere else?

    My issue is that when I try to

    1. Hi David,

      Glad you enjoyed the tutorials.

      ** I loose the ability to add the banner**

      Did you unchecked the banner field from the paragraph field on the basic page?

      ** page–node.html.twig doesn’t seem to work. **

      Make sure you place the twig file into the `templates` directory. Another thing you could do is turn on twig debugging, https://www.drupal.org/node/1906392, and see which template is being called.

      1. Hi Ivan,

        I didn’t manage the form display on the paragraphs widget on the basic page settings.
        I am all good now when it comes to creating a banner with images and content on a PAGE.

        Problem now is…

        When I implement the preprocess HOOK should it be in the ‘paragraphs.module’ or directly in the ‘preprocessor.module.php’ or maybe both?
        Currently I have it sitting in the paragraphs.module
        I then update the page.html.twig with your code and put it in my SUB Theme’s template folder and Clear Cache.


        My container is gone and all that’s left is my menu nav and footer content…

        Any ideas?

        1. Hi David,

          First regarding the preprocess.

          You can add it to a custom module or the .theme file in the theme directory. Do not add it to the paragraphs.module or any other modules from drupal.org.

          Now the page.html.twig.

          It’s hard to say, but check the following:

          – Are you implementing the default theme regions?
          – Have you modified the page.html.twig
          – The code snippet in this tutorial is just the “main” region. Grab the full file from the gist: https://gist.github.com/zugec/71af4faeea34db7e99bf#file-page-node-html-twig

          Hope this helps.

          1. Hi Ivan,

            I was wondering if there was any way to control the height of the banner background image WITHOUT adding a fixed height to a class within.

            As is:

            My module’s paragraph is defined by whats inside it (the content), so if I want to display the background image at the correct design height I have to add a class and then fix a height to that. Right now if there is one line of content there is one line of image displayed.

            Adding a fixed height is fine for desktops, but when the image scales down for mobile devices I then have to add additional heights at other breakpoints and it gets very messy and honestly doesn’t look too good. I’d prefer to hide the image in mobile layouts but can’t target specific paragraphs via CSS. Do you have any suggestions for a work around?

          2. Hi David,

            If you add an image as a background then the content in the inner DIV determines the height. You need to figure out what determines the height, is it content? Is it an image? Or is it a fixed height?

            One option is to have the image determine the height, however, this means you’ll need to display the image via an IMG tag instead as a background image style.

            This is all front-end development and has nothing to do with Paragraphs.

            Here are a few links I’ve found:



  4. david bastian

    Hi ivan, do you know if is possible use your module paragraphs with the api-rest of drupal, i have been tryiging to get the objects but , just i can get null elements..


    1. Hi David,

      I’ve never tried displaying paragraphs via a rest endpoint. A good place to ask would be in the module’s issue queue.

  5. THX for your explanations. i am trying to set the viewmode for a paragraph in the MODULE_preprocess_paragraph_TYPE Hook. my template gets only loaded with default viewmode.
    any idea?

    1. Hi Rafa,

      First of all, this only works for Drupal 8. Second, is something like Display Suite setup on your other view mode? If Display Suite is configured, then it’ll skip the preprocess function.

      If your preprocess is “HOOK_preprocess_paragraph__TYPE” then it should work on any view mode. However, Paragraphs does allow you to implement a preprocess on a specific type and view mode, i.e., “HOOK_preprocess_paragraph__TYPE__VIEWMODE”.


  6. Hi Ivan,

    sure i am investigating D8 only at the moment. as i try not to use displaysuite or panels, i wanted to understand how to preprocess data and provide it in my custom templates. i figured it out 🙂

    For paragraphs or any entity, we can define a theme suggestion via:

    Here we can add a suggestion, e.g.:
    $suggestions[] = ‘paragraph__’ . $type . ‘__’ . $specific_view_mode;
    Templates are selected accordingly, specific to general BUT
    preprocess funtions are called in a revers order, general to specificaly

    Now we can preprocess specificly via:

    before this will be called e.g.:

    We can make general preprocessing AND make specific changes.
    The modifications made to the $variables are availible through the preprocess hierarchie and inside the twig templates.

    please excuse my english 😉
    i hope this helps.

    Greetz rafa

    1. Thanks for the update.

      But doesn’t the paragraphs module already do this via the paragraphs_theme_suggestions_paragraph function?

      1. i was just investigating the wokflow. i have sometimes the case where paragraphs have a custom type field, e.g. a teaser type X / Y / Z using the same entity model with different themes and preprocessing.
        Adding a view_mode is achieves the same.

Leave a Comment

You have to agree to the comment policy.

This site uses Akismet to reduce spam. Learn how your comment data is processed.