Flexible table of contents using Views and Paragraphs

If you have long pages on your site, then it helps visitors if you can provide in-page navigation, or a table of contents.

In older versions of Drupal we used the Table of Contents filter module but while we loved the back to top functionality and the ability to customise the look and feel of the display was really useful. However, as we built more complex sites using the Paragraphs module we kept running into limitations with one of our favourite modules.

Contributed solutions

Our default position is to look for a contributed module rather than creating a custom module to meet our requirements. We investigated the possibility of using the Paragraphs Table of Contents to provide the in-page navigation. 

Most of the work is done through configuration: paragraph types, view modes, and a view to create the ToC block. There is a little custom code that helps with the theming, so that the entries in the table of contents are links to the corresponding sections on the page.

View modes: The "Table of Contents" view mode for paragraphs (toc) shows only the title (field_ptoc_title) and nested paragraphs (using the same view mode). Similarly, the "Table of Contents" view mode for nodes shows just the paragraphs field (field_ptoc_sections) in this view mode.

View: the view (ptoc_table_of_contents) creates a block using the node ID (nid) from the current page. That block simply displays the current node in "Table of Contents" view.

Custom code: A preprocess function adds an id attribute to paragraphs that use the default view mode, so that they can be targeted by links. Theme functions tell Drupal to use a custom Twig template for paragraphs that use the toc view mode. This template makes the title an in-page link to the default view of the paragraph.

While the module worked well the limitation of having to install another module and using a new view mode specifically for the table of contents meant that it was difficult to meet our requirements.

What we needed

We wanted to take the elements we liked from the Paragraphs Table of Contents module but make it more flexible for our requirements:

  1. Use the default view mode for multiple paragraph types
  2. Allow the user to determine if a Table of Contents should be added to a page and the placement of the Table of Contents on a page
  3. Use views to build the Table of Contents for easy customisation through the GUI

Building the foundations

Custom code

The only custom code is a preprocess function that adds an id attribute to paragraphs that use the default view mode, so that they can be targeted by links.

// Preprocess for paragraphs
function THEME_preprocess_paragraph(&$variables) {
  if ('default' == $variables['elements']['#view_mode']) {
    $variables['attributes']['id'] = 'paragraph-' . $variables['paragraph']->id();

Paragraph types

Our approach builds on our standardised approach to paragraphs - with each paragraph type having a standardised ‘Headline’ field that is reused across paragraph types.

In this example we will use two sample paragraph types: basic and video.

Basic Paragraph Type

Basic Paragraph type
Basic Paragraph Type

Video Paragraph Type

Video paragraph type
Video Paragraph type

Table of Contents

No fields added

Table of Contents view

We are big fans of the EVA: Entity Views Attachment to allows the output of a View to be attached to another entity - and to allow this ‘field’ to be reordered on the "Field Display" page for that entity.

The actual view

  1. Content view
  2. Add a relationship to the standard paragraph field used across all content types
  3. Add field links
    1. Paragraph ID - Hidden
    2. Paragraphs Headline - Rewrite results
      Headline rewritten code
      Rewritten field
  4. Add EVA display
    1. Attach to Paragraphs
    2. Bundle: Table of Contents
  5. Add contextual Filter
    1. Content ID: Default value Content ID from URL
  6. Add filter criteria
    1. Headline not empty

Putting it all together

  1. Page content type
    1. Title
    2. Summary
    3. Paragraphs field
      1. Allow all paragraphs
  2. Add a page with
    1. TOC Paragraph Block
    2. Basic content
    3. Video content
Sample page with Table of Contents
Page with table of contents



Rather than using a contributed module and adapting it to meet our needs we were able to build a more robust solution that meets our needs by reusing existing functionality and a simple piece of custom code to add an ID to each paragraph.

Our solution has limitations - it only works if the same general paragraphs field is used and each paragraph needs to reuse the same title field. However, these limitations can be overcome by extending the EVA view and provide a much more customisable solution that can be retrofitted to existing sites.