Outline

A scroll-spy table of contents, built from scratch (no IntersectionObserver ambiguity — a heading is "active" once its top has scrolled past offset, the last such heading in document order wins).

Real use · scroll-spy docs page (scroll the panel below)

Installation

Placeholder copy for the “Installation” section — long enough to make the panel actually scroll so the active link updates as you go.

Via npm

Placeholder copy for the “Via npm” section — long enough to make the panel actually scroll so the active link updates as you go.

Via CDN

Placeholder copy for the “Via CDN” section — long enough to make the panel actually scroll so the active link updates as you go.

Usage

Placeholder copy for the “Usage” section — long enough to make the panel actually scroll so the active link updates as you go.

Theming

Placeholder copy for the “Theming” section — long enough to make the panel actually scroll so the active link updates as you go.

Color tokens

Placeholder copy for the “Color tokens” section — long enough to make the panel actually scroll so the active link updates as you go.

Dark mode

Placeholder copy for the “Dark mode” section — long enough to make the panel actually scroll so the active link updates as you go.

FAQ

Placeholder copy for the “FAQ” section — long enough to make the panel actually scroll so the active link updates as you go.

Using Outline

Silica ships one design system through three paths. They share a class vocabulary but are not interchangeable — pick the one that matches what you are building.

Plain HTML — CSS classes

The Tailwind v4 plugin. Works in any framework or none — Rails, Django, PHP, Go, a static file. This path ships no JavaScript, so Outline is styled but inert: its behavior comes from toc. For that behavior with no framework, use the node-tree path below.

/* in your CSS */
@import "tailwindcss";
@plugin "@wizeworks/silicaui";

Root class outline, with 2 modifiers:

  • .outline
  • .outline-link
  • .outline-list

React

Typed components over those same classes, with behavior and accessibility from Base UI.

npm i @wizeworks/silicaui-react
import { Outline } from "@wizeworks/silicaui-react";

Props

PropTypeRequiredDescription
itemsOutlineItem[]yes—
offsetnumbernoDistance in px from the scroll container's top edge that counts as the "active" boundary — a heading is current once it's scrolled to here. Default 96 (roughly a sticky header's height).
containerReact.RefObject<HTMLElement | null>noScrollable ancestor to track. Default: the window (a real full-page TOC).
onActiveChange(id: string | null) => voidno—

Source of the demo above

The exact file that renders the examples above, so it can never drift from them. It imports a few of this site’s own layout helpers (../lib/…), so read it for the component calls rather than copying it whole.

Show source
import { useRef } from "react";
import { Outline } from "@wizeworks/silicaui-react";
import { Section } from "../lib/Section";

const SECTIONS = [
    { id: "outline-install", label: "Installation", depth: 0 },
    { id: "outline-npm", label: "Via npm", depth: 1 },
    { id: "outline-cdn", label: "Via CDN", depth: 1 },
    { id: "outline-usage", label: "Usage", depth: 0 },
    { id: "outline-theming", label: "Theming", depth: 0 },
    { id: "outline-tokens", label: "Color tokens", depth: 1 },
    { id: "outline-dark", label: "Dark mode", depth: 1 },
    { id: "outline-faq", label: "FAQ", depth: 0 },
];

export function OutlineDemo() {
    const containerRef = useRef<HTMLDivElement>(null);

    return (
        <Section title="Real use · scroll-spy docs page (scroll the panel below)">
            <div className="grid grid-cols-[1fr_12rem] gap-6">
                <div
                    ref={containerRef}
                    className="h-80 overflow-y-auto rounded-box border border-base-300 p-6"
                >
                    {SECTIONS.map((s) => (
                        <section key={s.id} id={s.id} className="mb-10 scroll-mt-4">
                            <h3 className="mb-2 text-base font-semibold">{s.label}</h3>
                            <p className="text-sm opacity-70">
                                Placeholder copy for the “{s.label}” section — long enough to
                                make the panel actually scroll so the active link updates as
                                you go.
                            </p>
                        </section>
                    ))}
                </div>

                <Outline items={SECTIONS} container={containerRef} offset={24} />
            </div>
        </Section>
    );
}

Vanilla — node tree to HTML

Outline — toc; unlike the React version (which derives its list from scanning heading elements at runtime), the vanilla macro is prop-driven like Breadcrumb/Steps/Timeline: props.items: {id, label}[] becomes the anchor links the toc behavior tracks via IntersectionObserver. Build a node tree, project it with toHtml(), and let the zero-dependency runtime hydrate it. This is the path for generated or user-authored documents — a site builder, CMS output, a static export.

npm i @wizeworks/silicaui-html @wizeworks/silicaui-behaviors
import { atom, toHtml } from "@wizeworks/silicaui-html";

const node = atom("Outline", "outline", { /* props */ });
const markup = toHtml(node);

Styling on this path is the class string in the second argument — the same classes as the Plain HTML section above. There is no color prop here; write "outline outline-primary" rather than passing a colour as a prop.

Takes children
no
Hydrated by
toc

Load @wizeworks/silicaui-behaviors on the page or this markup renders correctly and does nothing.

The full API for Outline, on every path, is available through the Silica MCP server.