Chapters

PreviousNext

A scroll-driven page layout that highlights the active chapter in a sticky sidebar as the user scrolls, with theme support for brand ranges

1 - All-inclusive experience

From sunrise yoga to late-night entertainment, every moment is included. Fine dining, premium beverages, world-class facilities — yours without a second thought.

2 - Sports & activities

Surf, dive, sail, climb — over 60 land and water sports await you. Our certified instructors guide guests of all levels, from first-timers to seasoned athletes.

3 - Kids clubs

Dedicated clubs for every age from 4 months to 17 years. Supervised activities, creative workshops, and adventure programmes tailored to each stage of childhood.

'use client';

import { Chapters } from '@/ui/Chapters/Chapters';

export function ChaptersDemo() {
  return (
    <Chapters
      chapters={[
        {
          title: 'All-inclusive experience',
          content: (
            <div className="min-h-screen p-40">
              <p className="text-body max-w-prose">
                From sunrise yoga to late-night entertainment, every moment is included. Fine
                dining, premium beverages, world-class facilities — yours without a second thought.
              </p>
            </div>
          ),
        },
        {
          title: 'Sports & activities',
          content: (
            <div className="min-h-screen p-40">
              <p className="text-body max-w-prose">
                Surf, dive, sail, climb — over 60 land and water sports await you. Our certified
                instructors guide guests of all levels, from first-timers to seasoned athletes.
              </p>
            </div>
          ),
        },
        {
          title: 'Kids clubs',
          content: (
            <div className="min-h-screen p-40">
              <p className="text-body max-w-prose">
                Dedicated clubs for every age from 4 months to 17 years. Supervised activities,
                creative workshops, and adventure programmes tailored to each stage of childhood.
              </p>
            </div>
          ),
        },
      ]}
      theme="coreRange"
      className="w-full"
    />
  );
}

Installation

npx shadcn@latest add https://develop.trident-ui.pro.clubmed/r/chapters.json

Usage

import { Chapters } from '@/ui/Chapters/Chapters';
 
export function Component() {
  return (
    <Chapters
      chapters={[
        {
          title: 'All-inclusive experience',
          content: <section className="min-h-screen p-40">…</section>,
        },
        {
          title: 'Sports & activities',
          content: <section className="min-h-screen p-40">…</section>,
        },
      ]}
      theme="coreRange"
    />
  );
}

Anatomy

Chapters is composed of three internal sub-components:

Sub-componentRole
ChaptersSummarySticky sidebar <nav> with one link per chapter; hidden on mobile
ChapterWrapper <section> that observes its own intersection to report active state
ChapterTitleSticky mobile heading that anchors each chapter section

API Reference

Chapters

PropTypeDefaultDescription
chaptersChapterItem[]RequiredArray of chapter objects; each has a title and arbitrary content
themeTheme'coreRange'Visual theme — controls sidebar and title background/text colours
hLevel2 | 3 | 42Heading level for each chapter title (<h2> by default)
iconIconicNamesundefinedOverride the default brand icon shown at the bottom of the sidebar
onClick(chapter: ChapterItem) => voidundefinedCalled when a chapter title or sidebar link is clicked
classNamestringundefinedAdditional CSS classes on the root <section>

ChapterItem

FieldTypeDescription
titlestringChapter heading text and sidebar label
contentReactNodeArbitrary content rendered below the title

Themes

The theme prop switches the sidebar and mobile title bar colours. Supported values:

ValueBackgroundTextIcon
coreRange (default)lightSandblackTrident
exclusiveCollectionultramarinewhiteExclusiveCollection
happyToCareverdigrisblackHappyToCare
siennasiennawhiteTrident
blackblackwhiteTrident
whitewhiteblackTrident
lightlightSandblackTrident
darkultramarinewhiteExclusiveCollection

Behaviour

Scroll-driven active chapter

Each Chapter section registers an IntersectionObserver against its root element. The active chapter index is determined by which section satisfies one of three visibility rules:

  1. Section height ≤ half the viewport and intersection height > one-third the viewport
  2. Section height ≤ half the viewport and section is fully visible
  3. Section height > half the viewport and intersection height > half the viewport

The matching chapter's sidebar link is displayed at full opacity; inactive links are shown at 50 %.

Mobile sticky titles

On mobile, each chapter title sticks to the top of the viewport as the user scrolls through that section, allowing orientation without an always-visible sidebar.

Anchor-based navigation

Sidebar links and the "next chapter" arrow anchor to #chapter{index}, enabling in-page keyboard navigation and shareable deep links.

Accessibility

  • The sidebar is a <nav> landmark; screen readers announce it as navigation.
  • Each chapter is a <section> element — equivalent to a region landmark when given an accessible name via its id.
  • Chapter titles are rendered as <h2> (configurable via hLevel), providing document outline structure for assistive technology.
  • The "next chapter" arrow on mobile is an <a> element targeting the subsequent chapter anchor.
  • The sidebar brand icon is purely decorative (no accessible name required).

Notes

  • All four files (Chapters, Chapter, ChapterTitle, ChaptersSummary) are marked 'use client' — they depend on useState, useEffect, and browser APIs.
  • IntersectionObserver is used directly; it is not available in JSDOM. Tests must provide a stub — see the spec file for the pattern.
  • The sidebar is hidden on mobile (hidden md:block) and shown at md breakpoint and above. On mobile, ChapterTitle provides equivalent navigation via sticky headings and prev/next anchors.
  • The optional icon prop accepts any IconicNames value from @clubmed/trident-icons. When omitted, the theme's default icon is used (usually Trident, or the brand-specific icon).