frontend

You can get the paragraphs in your nodes ready for the ToC module using these Twig templates.

field--field-paragraphs.html.twig
{% extends "field--no-container.html.twig" %}
{% block template %}
  <div class="toc-content">
    {{ parent() }}
  </div>
{% endblock %}
field--no-container.html.twig
{#
/**
 * @file
 * Theme override for a field.
 *
 * To override output, copy the "field.html.twig" from the templates directory
 * to your theme's directory and customize it, just like customizing other
 * Drupal templates such as page.html.twig or node.html.twig.
 *
 * Instead of overriding the theming for all fields, you can also just override
 * theming for a subset of fields using
 * @link themeable Theme hook suggestions. @endlink For example,
 * here are some theme hook suggestions that can be used for a field_foo field
 * on an article node type:
 * - field--node--field-foo--article.html.twig
 * - field--node--field-foo.html.twig
 * - field--node--article.html.twig
 * - field--field-foo.html.twig
 * - field--text-with-summary.html.twig
 * - field.html.twig
 *
 * Available variables:
 * - attributes: HTML attributes for the containing element.
 * - label_hidden: Whether to show the field label or not.
 * - title_attributes: HTML attributes for the title.
 * - label: The label for the field.
 * - multiple: TRUE if a field can contain multiple items.
 * - items: List of all the field items. Each item contains:
 *   - attributes: List of HTML attributes for each item.
 *   - content: The field item's content.
 * - entity_type: The entity type to which the field belongs.
 * - field_name: The name of the field.
 * - field_type: The type of the field.
 * - label_display: The display settings for the label.
 *
 *
 * @see template_preprocess_field()
 */
#}
{% block template %}
  {% for item in items %}
    {{ item.content }}
  {% endfor %}
{% endblock %}

Option 1:

table--color-border.scss
:root {
  --Color-Border: black;
  --Color-Background: white;
  --Spacing-01: 1rem;
  --Spacing-02: 1.5rem;
}

body {
  background: var(--Color-Background);
}

.table--color-border {
  border-collapse: collapse;

  tbody th {
    text-align: left;
  }

  thead th,
  tbody th {
    padding: var(--Spacing-01) var(--Spacing-02) var(--Spacing-01) calc(25px / 2);

    &:first-child {
      padding-left: 0;
    }
  }

  thead th {
    border: none;
    box-shadow: inset 0 -1px 0 0 var(--Color-Border),
    inset 0 1px 0 0 var(--Color-Border);
    text-align: left;
  }

  tbody td {
    text-align: center;
  }

  tbody th,
  tbody td {
    font-weight: 400;
    border-right: 25px solid var(--Color-Background);
    border-bottom: 1px solid var(--Color-Border);
    width: max-content;

    &:last-child {
      border-right: none;
    }
  }
}

.table--pseudo-elements {
  border-collapse: collapse;

  thead th {
    border-top: 1px solid var(--Color-Border);
    border-bottom: 1px solid var(--Color-Border);
  }

  td,
  th {
    padding: var(--Spacing-02) var(--Spacing-01);
  }

  td,
  th:not(thead th) {
    position: relative;
    text-align: center;

    &:first-child {
      text-align: left;
      padding: var(--Spacing-02) var(--Spacing-01) var(--Spacing-02) 0;

      &::after {
        content: '';
        position: absolute;
        left: 0;
        bottom: 0;
        width: calc(100% - var(--Spacing-01));
        border-bottom: 1px solid var(--Color-Border);
      }
    }

    &:last-child {
      padding: var(--Spacing-02) 0 var(--Spacing-02) var(--Spacing-01);

      &::after {
        content: '';
        position: absolute;
        left: 0.5rem;
        bottom: 0;
        width: calc(100% - var(--Spacing-01));
        border-bottom: 1px solid var(--Color-Border);
      }
    }

    &::after {
      content: '';
      position: absolute;
      left: var(--Spacing-01);
      bottom: 0;
      width: calc(100% - var(--Spacing-02));
      border-bottom: 1px solid var(--Color-Border);
    }
  }
}

How it works

border-bottom: 1px solid black;
border-right: 25px solid white;

The right border isn't really intended as a border. It's acting as a fake column gap because its color matches the page background.

Disadvantage: depends on matching background color

Option 2:

table--pseudo-elements.scss
:root {
  --Color-Border: black;
  --Spacing-01: 1rem;
  --Spacing-02: 1.5rem;
}

.table--pseudo-elements {
  border-collapse: collapse;

  thead th {
    border-top: 1px solid var(--Color-Border);
    border-bottom: 1px solid var(--Color-Border);
  }

  td,
  th {
    padding: var(--Spacing-02) var(--Spacing-01);
  }

  td,
  th:not(thead th) {
    position: relative;
    text-align: center;

    &:first-child {
      text-align: left;
      padding: var(--Spacing-02) var(--Spacing-01) var(--Spacing-02) 0;

      &::after {
        content: '';
        position: absolute;
        left: 0;
        bottom: 0;
        width: calc(100% - var(--Spacing-01));
        border-bottom: 1px solid var(--Color-Border);
      }
    }

    &:last-child {
      padding: var(--Spacing-02) 0 var(--Spacing-02) var(--Spacing-01);

      &::after {
        content: '';
        position: absolute;
        left: 0.5rem;
        bottom: 0;
        width: calc(100% - var(--Spacing-01));
        border-bottom: 1px solid var(--Color-Border);
      }
    }

    &::after {
      content: '';
      position: absolute;
      left: var(--Spacing-01);
      bottom: 0;
      width: calc(100% - var(--Spacing-02));
      border-bottom: 1px solid var(--Color-Border);
    }
  }
}

How it works

Instead of using actual borders, every cell draws its own Pseudo-element border and reduced the width which creates the visual gap.
width: calc(100% - var(--Spacing-02));

Disadvantage: much more code and maintenance

Option 3 (preferred):

table--seperated-borders.scss
:root {
  --Color-Border: black;
  --Spacing-01: 1rem;
  --Spacing-02: 1.5rem;
}

.table--seperated-borders {
  border-collapse: separate;
  border-spacing: var(--Spacing-02) 0;
  margin-left: calc(-1 * var(--Spacing-02));
  margin-right: calc(-1 * var(--Spacing-02));
  
  thead {
    border: none;
    box-shadow: inset 0 -1px 0 0 var(--Color-Border),
      inset 0 1px 0 0 var(--Color-Border);
  }
  
  tbody td {
    text-align: center;
  }
  
  td,
  th {
    border-bottom: 1px solid var(--Color-Border);
    padding: var(--Spacing-02) var(--Spacing-01) var(--Spacing-02) 0;
  }
}

How it works

Uses the table model as intended. The browser creates real gaps between cells. Then each cell simply gets border-bottom: 1px solid black;. But this creates spacing outside the first and last column too.

This is why it is compensating with:

margin-left: calc(-1 * var(--Spacing-02));
margin-right: calc(-1 * var(--Spacing-02));

The thead gets a solid line via box-shadow.

Disadvantage: creates outer gaps that often require compensation

package.json
{
  "name": "Foo project",
  "scripts": {
    "sass-dev": "sass --watch --update --style=expanded src/scss:dist/css",
    "sass-prod": "sass --no-source-map --style=compressed src/scss:dist/css"
  },
  "devDependencies": {
    "sass": "^1.86.0"
  }
}
13.11.2025 | Holger Weischenberg

Simple SCSS compiler setup