Layout Composition

Era Strata Layout

An accordion layout that stacks eras or phases into horizontal strata — the expanded band opens vertically while the rest compress, and the content inside floats up with parallax.

This is a WebberUI Pro component

The install command below needs a licence to run; without one it returns 401.

How to install Pro components →See the plans →

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add "https://webberui.com/r/era-strata-layout.en.json?t=<install token>"

New project or installation trouble? Start here →

Era Strata Layout is a React vertical accordion timeline layout built with Motion and Tailwind CSS. Each era or phase is a horizontal band, and the expanded band can be switched by click, hover, or page scroll.

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

440
72
Active edited usageDefault props
<EraStrataLayout/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

When to use

  • Company history and milestones on an About page
  • A phased walkthrough of a product's history or version evolution
  • Chapters that advance by era in a brand story or long-form feature
  • Ordered stages such as a course outline or an onboarding process
  • Not a good fit: unrelated sections like Q&A or settings where several may be open at once; use Accordion instead

Installation

npx webberui@latest add "https://webberui.com/r/era-strata-layout.en.json?t=<install token>"

Or, once registries are configured in components.json, install it as @webberui/era-strata-layout.

Usage

import {
  EraStrataLayout,
  type EraStratumData,
} from "@/components/ui/era-strata-layout";

const eras: EraStratumData[] = [
  {
    id: "1998",
    era: "1998",
    title: "Founded in a garage",
    summary: "Three people and one server.",
    content: "The first version shipped late at night.",
    accentClassName: "bg-amber-400 dark:bg-amber-500",
  },
  // ...
];

<EraStrataLayout items={eras} height={440} defaultActive={0} />;

trigger switches how expansion is triggered: click (the default), hover to expand on hover, or scroll to advance stratum by stratum as the page scrolls.

// Advance on scroll: when scrolling inside a nested container, pass that container's ref to container
<EraStrataLayout items={eras} trigger="scroll" height={480} />

Examples

Control the expanded band with external buttons

Pass active and onActiveChange to enter controlled mode and pair it with previous/next buttons (controlled mode applies to the click and hover triggers).

"use client";

import * as React from "react";
import { EraStrataLayout, type EraStratumData } from "@/components/ui/era-strata-layout";

export function CompanyHistory({ eras }: { eras: EraStratumData[] }) {
  const [active, setActive] = React.useState(0);

  return (
    <div className="space-y-4">
      <EraStrataLayout items={eras} active={active} onActiveChange={setActive} />
      <div className="flex gap-2">
        <button onClick={() => setActive((i) => Math.max(0, i - 1))}>Previous stage</button>
        <button onClick={() => setActive((i) => Math.min(eras.length - 1, i + 1))}>
          Next stage
        </button>
      </div>
    </div>
  );
}

Expand on hover with custom content

content and summary accept any React node, so lists or images work; trigger="hover" expands a band on mouse hover or keyboard focus.

const roadmap: EraStratumData[] = [
  {
    id: "q1",
    era: "Q1",
    title: "Public beta",
    summary: "Opened to 500 early users.",
    content: (
      <ul className="list-disc pl-5 text-sm">
        <li>Accounts and team permissions</li>
        <li>Basic report export</li>
      </ul>
    ),
    accentClassName: "bg-sky-500",
  },
  {
    id: "q2",
    era: "Q2",
    title: "General availability",
    summary: "Sign-up opened to everyone.",
    accentClassName: "bg-emerald-500",
  },
];

<EraStrataLayout items={roadmap} trigger="hover" height={360} collapsedSize={64} />

Advance on scroll inside a nested scroll container

When scrolling happens inside its own overflow container rather than the page, pass that container's ref to container.

"use client";

import * as React from "react";
import { EraStrataLayout, type EraStratumData } from "@/components/ui/era-strata-layout";

export function ScrollPanel({ eras }: { eras: EraStratumData[] }) {
  const panelRef = React.useRef<HTMLDivElement>(null);

  return (
    <div ref={panelRef} className="h-[480px] overflow-y-auto">
      <EraStrataLayout items={eras} trigger="scroll" height={480} container={panelRef} />
    </div>
  );
}

Props

PropTypeDefaultDescription
itemsEraStratumData[]—Strata data, stacked top to bottom in order
activenumber—Controlled expanded index (applies in click / hover modes)
defaultActivenumber0Initial expanded index in uncontrolled mode
onActiveChange(index: number) => void—Fires when the expanded index changes
trigger"click" | "hover" | "scroll""click"How expansion is triggered
heightnumber | string440Total layout height (a number means px)
collapsedSizenumber72Height of a collapsed stratum / title bar (px)
parallaxbooleantrueWhether the content floats up with parallax
containerRefObject<HTMLElement | null>—Nested scroll container ref for scroll mode
labelstring"Chronological Stratigraphy"Accessible label
classNamestring—Class on the outer container
stratumClassNamestring—Class applied to each stratum band

EraStratumData

FieldTypeDescription
idstringStable identifier, used as the accessibility anchor and the key
eraReactNodeEra / phase marker, still visible when collapsed
titleReactNodeSection title, still visible when collapsed
summaryReactNodeThe one-line summary that floats up first on expand
contentReactNodeThe main content that floats up with parallax on expand
accentClassNamestringAccent color class for the stratum color bar on the left

How it works

  • The expanded stratum opens vertically on a spring transition while the rest compress back to collapsedSize in sync, leaving only their title bars visible
  • Remaining space is distributed with flexbox flex-grow, and exactly one stratum is expanded at a time; make sure height ≥ collapsedSize × number of strata
  • Inside a stratum, summary and content float up in sequence from different starting offsets to create parallax layering; with parallax={false} only the fade in remains
  • scroll mode extends the matching scroll height and pins to the viewport, mapping scroll progress onto the expanded stratum; clicking a stratum smoothly scrolls to it

Accessibility

  • Each stratum title is a native button with aria-expanded and aria-controls, and the content area is a role="region" labeled by its title through aria-labelledby
  • The title bars use a roving tabindex: after Tab moves in, ↑ / ↓ (or ← / →) move between strata, Home / End jump to the first and last, and Enter / Space expand
  • Collapsed content areas are marked inert and aria-hidden, so neither the keyboard nor assistive technology wanders into them
  • When the user has "reduce motion" enabled at the system level, it becomes a fully expanded static list showing all the content at once, with no compression and no animation

FAQ

Does it work with the Next.js App Router?

Yes. The file already starts with "use client", so you can import it from a Server Component page as usual. With reduced motion enabled, the component switches to its static version only after mounting, so the first render matches the SSR output.

How much page height does scroll mode take?

The wrapper is height multiplied by the number of bands, with a height-tall viewport pinned by sticky; scroll progress maps to each band in turn. Scroll mode needs a numeric height; with a string it falls back to 440 to compute the scroll distance.

Why are the bands cramped or the content cut off?

Collapsed bands are fixed at collapsedSize, and the expanded band gets the remaining space. Make sure height is at least collapsedSize times the number of bands, with enough room left for the expanded content.

How do I change the colours?

Set each band's left accent bar with accentClassName, for example "bg-amber-400 dark:bg-amber-500"; adjust the overall look with className and the per-band stratumClassName.

Does it support keyboards and screen readers?

Yes. Headers are native buttons with aria-expanded; arrow keys move between bands and Home/End jump to the first and last. Collapsed content regions are marked inert so focus can't land in them.