Skip to main content
react-md

useActiveHeadingId

function useActiveHeadingId(options: ActiveHeadingIdOptions): string;

The useActiveHeadingId hook can be used to determine which heading group is visible within the viewport and normally used with a scrollable table of contents. This hook is heavily inspired by the Mozilla Docs Table of Contents behavior and is used for this website's Table of Contents view on non-mobile devices.

Example Usage

The useActiveHeadingId requires providing a list of heading ids for the page to enable creating intersection observer events when the heading is scrolled into view. It is recommended to use this hook with mdx/rehype but can be used for other applications as well. Here's a small example for adding a dynamic table of contents in a client-side app.

Check out this website's table of contents and rehype-toc implementation to see how it can integrate into MDX and rehype.

"use client";

import { useSsr } from "@react-md/core/SsrProvider";
import { type HeadingReferenceWithChildren } from "@react-md/core/navigation/types";
import { useActiveHeadingId } from "@react-md/core/navigation/useActiveHeadingId";
import { useTableOfContentsHeadings } from "@react-md/core/navigation/useTableOfContentsHeadings";
import { Typography } from "@react-md/core/typography/Typography";
import { RenderRecursively } from "@react-md/core/utils/RenderRecursively";
import { useId } from "react";

import styles from "./TableOfContents.module.scss";

export function TableOfContents() {
  const headingId = useId();
  const ssr = useSsr();
  const headings = useTableOfContentsHeadings();

  const activeHeadingId = useActiveHeadingId({ headings });
  return (
    <nav aria-labelledby={headingId} className={styles.container}>
      <Typography id={headingId} type="headline-5" margin="none">
        Table of Contents
      </Typography>
      <TableOfContentsGroup root>
        <RenderRecursively
          data={activeHeadingId}
          items={transformToItems(toc)}
          render={RenderTableOfContentsItem}
          getItemKey={({ item }) => item.id}
        />
      </TableOfContentsGroup>
    </nav>
  );
}

Parameters

export interface ActiveHeadingIdOptions {
  headings: readonly HeadingReferenceWithChildren[];

  /** @see {@link DEFAULT_ACTIVE_HEADING_THRESHOLD} */
  threshold?: IntersectionObserverThreshold;

  /** @see {@link DEFAULT_ACTIVE_HEADING_GET_ROOT_MARGIN} */
  getRootMargin?: () => IntersectionObserverRootMargin;

  /** @defaultValue `headings[0]?.id ?? ""` */
  defaultActiveId?: UseStateInitializer<string>;

  /** @defaultValue `0.8` */
  scrollBottomThreshold?: number;
}

export interface HeadingReference {
  id: string;
}

export interface HeadingReferenceWithChildren extends HeadingReference {
  children?: readonly HeadingReferenceWithChildren[];
}

Returns

The id for the heading that is currently "active" by having content mostly visible within the viewport.

See Also