Skip to content

code-tabs and tabs shortcodes render tab-label fragments with no matching id #7703

Description

@jstirnaman

What's happening

The code-tabs/code-tabs-wrapper/code-tab-content shortcodes
(layouts/shortcodes/code-tab*.html) and the equivalent
tabs/tabs-wrapper/tab-content shortcodes render tab links as
Markdown fragment links, for example:

{{% code-tabs %}}
[Linux](#linux)
[macOS](#macos)
[Homebrew](#homebrew)
{{% /code-tabs %}}

assets/js/tabbed-content.js switches tabs entirely by DOM position
($(tab).index(this) matched against the same index in .code-tab-content)
and calls e.preventDefault() on click, so the #linux-style href is never
actually followed. No shortcode in this set renders a matching id
attribute anywhere in the page, so these fragments don't correspond to any
real anchor in the rendered HTML.

Why it matters

.ci/link-checker (lychee-based, run in pr-link-check.yml) validates
fragments and correctly reports these as broken: Fragment not found: #linux. Confirmed this isn't a link-checker bug by pulling PR #7701's
actual CI run artifact: the URL reaches lychee as a literal #linux, not
a file://...#linux path, and no id anywhere in the rendered page matches
it. (Also found in the process: production.lycherc.toml's [reporting] include_fragments = false doesn't actually suppress fragment checking —
the real CI command always carries --include-fragments regardless of
that setting, so it isn't wired to anything in the current link-checker
binary. Worth its own look, but unrelated to this shortcode issue.)

Because these shortcodes are widely used, this is a landmine, not an
isolated case:

  • code-tabs-wrapper: used in 170 content files
  • tabs-wrapper: used in 313 content files

Any PR that touches a page using either shortcode risks an unrelated
"Fragment not found" CI failure on a tab label the PR never wrote to.
#7701 hit this on [Linux](#linux) / [macOS](#macos) /
[Homebrew](#homebrew) purely from moving an existing page into shared
content — the fragments were already broken, just never checked before.

Proposed direction

Render a real id on each tab's content section, derived from the tab
label's slug, so #linux resolves to an actual element — for example on
<section class="code-tab-content"> in code-tab-content.html /
tab-content.html. This shouldn't change tabbedContent()'s
index-based click switching, since ids are additive; it also improves
direct/shared links to a specific tab, which currently only work via the
?t= query param.

Needs care since both shortcodes are shared across hundreds of pages:

  • Confirm the slug derivation can't collide with an existing heading id
    on the same page.
  • Add or extend Cypress e2e coverage for tab switching and fragment
    navigation before landing (see .agents/skills/cypress-e2e-testing).
  • Verify rendered HTML for a representative sample of pages, not just
    Hugo's exit code.

Verification once fixed

  • link-checker check reports no "Fragment not found" errors for
    code-tabs/tabs anchors across the affected pages.
  • Existing tab-switching behavior (index-based click handling, ?t=
    query param persistence) is unchanged.
Relevant URLs

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:linkslink management (validation, checking, fixing)area:site-uiDocumentation site UI: templates, styles, JS/TS

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions