Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
markup/tableofcontents: Add pre/post config options
Add Pre and Post configuration options to customize the HTML wrapper
around the table of contents. This allows users to add custom elements
like headings for accessibility.

Example config:
[markup.tableOfContents]
  pre = '<nav id="TableOfContents"><h2>Contents</h2>'
  post = '</nav>'

The pre/post parameters are optional in ToHTML() for backward
compatibility - existing templates continue to work without changes.

Fixes #8338
  • Loading branch information
nathannewyen committed Dec 28, 2025
commit 44539bc05ad979d5bcbe6edc6d4e7d273eaa875b
2 changes: 2 additions & 0 deletions hugolib/page__content.go
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,8 @@ func (c *cachedContentScope) contentToC(ctx context.Context) (contentTableOfCont
cfg.TableOfContents.StartLevel,
cfg.TableOfContents.EndLevel,
cfg.TableOfContents.Ordered,
cfg.TableOfContents.Pre,
cfg.TableOfContents.Post,
)
return err
}
Expand Down
31 changes: 28 additions & 3 deletions markup/tableofcontents/tableofcontents.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ func (toc *Fragments) addAt(h *Heading, row, level int) {
}

// ToHTML renders the ToC as HTML.
func (toc *Fragments) ToHTML(startLevel, stopLevel any, ordered bool) (template.HTML, error) {
// The pre and post parameters are optional and default to the nav wrapper if not provided.
func (toc *Fragments) ToHTML(startLevel, stopLevel any, ordered bool, prePost ...string) (template.HTML, error) {
if toc == nil {
return "", nil
}
Expand All @@ -163,12 +164,24 @@ func (toc *Fragments) ToHTML(startLevel, stopLevel any, ordered bool) (template.
return "", fmt.Errorf("stopLevel: %w", err)
}

// Use default pre/post values if not provided
pre := DefaultConfig.Pre
post := DefaultConfig.Post
if len(prePost) > 0 && prePost[0] != "" {
pre = prePost[0]
}
if len(prePost) > 1 && prePost[1] != "" {
post = prePost[1]
}

b := &tocBuilder{
s: strings.Builder{},
h: toc.Headings,
startLevel: iStartLevel,
stopLevel: iStopLevel,
ordered: ordered,
pre: pre,
post: post,
}
b.Build()
return template.HTML(b.s.String()), nil
Expand All @@ -187,16 +200,18 @@ type tocBuilder struct {
startLevel int
stopLevel int
ordered bool
pre string
post string
}

func (b *tocBuilder) Build() {
b.writeNav(b.h)
}

func (b *tocBuilder) writeNav(h Headings) {
b.s.WriteString("<nav id=\"TableOfContents\">")
b.s.WriteString(b.pre)
b.writeHeadings(1, 0, b.h)
b.s.WriteString("</nav>")
b.s.WriteString(b.post)
}

func (b *tocBuilder) writeHeadings(level, indent int, h Headings) {
Expand Down Expand Up @@ -260,6 +275,8 @@ var DefaultConfig = Config{
StartLevel: 2,
EndLevel: 3,
Ordered: false,
Pre: `<nav id="TableOfContents">`,
Post: "</nav>",
}

type Config struct {
Expand All @@ -274,4 +291,12 @@ type Config struct {

// Whether to produce a ordered list or not.
Ordered bool

// Pre is the HTML to insert before the ToC list.
// Default is `<nav id="TableOfContents">`.
Pre string

// Post is the HTML to insert after the ToC list.
// Default is `</nav>`.
Post string
}