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:
'level' => <int>: Heading level of the section represented (Corresponds toh1,h2,h3, ...).'label' => <string>: Section label in the TOC (usually the content of theh1,h2, ... tags).'anchor' => <string>: Id of the section in the DOM, used by the component to know where to scroll to when the entry is clicked.
<x-toc> highlights its entries based on which content is predominant on screen.That content is determined by whatever is the lowest heading above the horizontal line located 25% from the top of the screen.
2 hardcoded rule enforce the highlight to be on the first or last item, respectively when the scrolling is all the way up or all the way down of the scrollbar.
This leaves a small possibility for an entry to never be highlighted. For instance, if the second-to-last and last sections have content so short the second-to-last heading never reaches that 25% screen threshold.
#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-mutatorIn 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"/>