Table of contents

Show sections of the current page and browse between them with links. The menu is synchronized with where the scrolling currently is located on the page.

<x-toc title="Table of contents" :headings="$headings" />
headings = [
	['level' => 1, 'label' => 'Table of contents', 'anchor' => '#'],
	['level' => 2, 'label' => 'Colors', 'anchor' => 'colors'],
	['level' => 2, 'label' => 'The selector attribute', 'anchor' => 'the-selector-attribute'],
	['level' => 2, 'label' => 'Examples', 'anchor' => 'examples'],
	['level' => 3, 'label' => 'Responsive ToC', 'anchor' => 'responsive-toc'],
	['level' => 3, 'label' => 'Automating ToC creation from Statamic Bard fieldtype', 'anchor' => 'automatic-toc-creation-from-statamic-bard-fieldtype'],
];

The <x-toc> component expects an array to be fed into its headings attribute. The value should be an array of associative arrays, each with the entries:

#Colors

The color attribute affects the highlight of the currently vizualized item.

<x-toc title="Table of contents" :headings="$headings" color="neutral" />
<x-toc title="Table of contents" :headings="$headings" color="primary" />
<x-toc title="Table of contents" :headings="$headings" color="secondary" />
<x-toc title="Table of contents" :headings="$headings" color="accent" />
<x-toc title="Table of contents" :headings="$headings" color="info" />
<x-toc title="Table of contents" :headings="$headings" color="success" />
<x-toc title="Table of contents" :headings="$headings" color="warning" />
<x-toc title="Table of contents" :headings="$headings" color="error" />
headings = [
	['level' => 1, 'label' => 'Table of contents', 'anchor' => '#'],
	['level' => 2, 'label' => 'Colors', 'anchor' => 'colors'],
	['level' => 2, 'label' => 'The selector attribute', 'anchor' => 'the-selector-attribute'],
	['level' => 2, 'label' => 'Examples', 'anchor' => 'examples'],
	['level' => 3, 'label' => 'Responsive ToC', 'anchor' => 'responsive-toc'],
	['level' => 3, 'label' => 'Automating ToC creation from Statamic Bard fieldtype', 'anchor' => 'automatic-toc-creation-from-statamic-bard-fieldtype'],
];

#The selector attribute

A table of contents must be able to find the ids of the sections present on the page. By default, it assumes any heading tag (h1 to h6) with an id is linked to an entry in the ToC. That is why by default, the selector is h1[id],h2[id],h3[id],h4[id],h5[id],h6[id].

The selector attribute allows you to adapt this to your page. If you give ids to e.g. <section> tags instead of headings, then you will need to set the selector attribute so that it finds them.

#Examples

#Responsive ToC

Most pages on this website adopt the same approach regarding tables of contents: show one in the margin next to the main content of the page low enough to stay under the menubar or, if the screen is not wide enough for a margin, fall back to a dropdown activated by a button.

<x-toc aria-hidden="true" class="preview h-full absolute left-[calc(100%_+_2rem)] *:top-24 hidden 2xl:block" :headings="$headings" title="On this page"/>
<div class="preview 2xl:hidden absolute right-0 h-full z-11">
  <div class="sticky top-24">
	<x-dropdown align="end">
	  <x-slot:trigger>
		<x-button color="neutral" variant="soft" aria-label="Table of contents" icon="heroicon-o-bars-3" class="aspect-square p-0"/>
	  </x-slot:trigger>
	  <x-toc aria-hidden="true" class="border-1 border-base-300 rounded-md" :headings="$headings"/>
	</x-dropdown>
  </div>
</div>
headings = [
	['level' => 1, 'label' => 'Table of contents', 'anchor' => '#'],
	['level' => 2, 'label' => 'Colors', 'anchor' => 'colors'],
	['level' => 2, 'label' => 'The selector attribute', 'anchor' => 'the-selector-attribute'],
	['level' => 2, 'label' => 'Examples', 'anchor' => 'examples'],
	['level' => 3, 'label' => 'Responsive ToC', 'anchor' => 'responsive-toc'],
	['level' => 3, 'label' => 'Automating ToC creation from Statamic Bard fieldtype', 'anchor' => 'automatic-toc-creation-from-statamic-bard-fieldtype'],
];

#Automating ToC creation from Statamic Bard fieldtype

If you render your page from a Bard fieldtype in Statamic, you will need the table of content to be generated dynamically. Hopefully, this task is easily done.

As headings need to be given an id for the ToC to reference, you need to install the Bard mutator addon.

composer require jacksleight/statamic-bard-mutator

In your AppServiceProvider.php file, add the following function.

Mutator::html('heading', function ($value, $data) {
    $value[1]['id'] = Str::slug(collect($data->content ?? [])->implode('text', ''));    
    return $value;
});

If you need to accomodate for the space taken by a menu bar at the top of your page, like on this site, you will need to add a scroll-margin class to $value. For instance:

$value[1]['class'] = "scroll-mt-20";

The next step is to build the $headings variable that to pass to the table of contents. This is easily done with the Bard Items modifier. The below example includes an entry for the h1 heading.

@php
$bard_headings = $content->raw() ? Statamic::modify($content)?->bardItems()?->where('type:heading') : [];

$headings = [['anchor' => '#', 'label' => $title, 'level' => 1]];
foreach ($bard_headings as $heading) {
$label = $heading['content'][0]['text'];
array_push($headings, ['anchor' => Str::slug(collect($label)->implode('text', '')), 'label' => $label, 'level' => $heading['attrs']['level']]);
}
@endphp

<x-toc aria-hidden="true" :headings="$headings" title="Generated from Bard fieldtype"/>