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:
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:
$ 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:
{# 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 }} </div> {% 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 %} </div> </div> {# Content #} <section> {# Content #} {% block content %} <a id="main-content"></a> {{ page.content }} {% endblock %} </section> </div> {% 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', 'paragraph--type--' ~ paragraph.bundle|clean_class, view_mode ? 'paragraph--view-mode--' ~ view_mode|clean_class, 'container', ] %} <div{{ attributes.addClass(classes) }}> {{ content }} </div>
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:
Summary
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.
FAQ
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.
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?
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.
**Third**
Add some basic conditional code in `page.html.twig`. like the code below:
**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.
Cheers,
Ivan
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 🙂
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
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.
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.
Boom…
My container is gone and all that’s left is my menu nav and footer content…
Any ideas?
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.
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..
Thanks!.
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.
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?
THX.
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”.
Cheers,
Ivan
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:
MODULE_theme_suggestions_ENTITY_alter
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:
MODULE_preprocess_paragraph__$type__$specific_view_mode
before this will be called e.g.:
MODULE_preprocess_paragraph__$type
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
Thanks for the update.
But doesn’t the paragraphs module already do this via the paragraphs_theme_suggestions_paragraph function?
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.