Flame Chart
A generic call-stack flame chart that paints each function frame as a horizontal block, stacked vertically by call depth and positioned horizontally by start time. Hover or focus any frame to surface its label, duration, depth, and start in the detail panel. Drop it into a JS-perf walkthrough, a tracing lesson, or any "where did the time go?" article.
Preview
render() call stack
Hover any frame to surface its label and timing detail.
0s100ms200ms300ms400ms500ms600ms
hover a frame to surface its detail
600ms window4 long frames
Customize
Timeline
600ms
220ms
50ms
Options
Installation
npx shadcn@latest add https://craftbits.dev/r/flame-chart.jsonUsage
import { FlameChart } from "@craft-bits/core";
<FlameChart
title="render() call stack"
windowMs={600}
frames={[
{ id: "root", label: "render()", depth: 0, start: 0, duration: 600 },
{ id: "app", label: "<App />", depth: 1, start: 40, duration: 480 },
{ id: "diff", label: "diff()", depth: 2, start: 80, duration: 220 },
]}
/>Anatomy
- Header. Optional
title(rendered with thecb-labelstyle) and adescriptionsub-line. Omit both for a chromeless panel. - Ruler. A row of tick marks across the top of the chart, auto-sized to the timeline window. Hide it with
hideRuler. - Track. A single relative-positioned block sized by
windowMswide and(maxDepth + 1) * rowHeightPxtall. Each frame is positioned absolutely by itsstart,duration, anddepth. - Block tone. Frames longer than
longTaskThresholdMspaint with--cb-error; the rest paint with--cb-accent. Each block carriesdata-cb-verdict="long" | "short"so consumers can extend the palette. - Detail panel. Sits under the chart and surfaces the active frame's label, duration, depth, start, and optional
detailbody. Reads "hover a frame to surface its detail" when nothing is active. - Legend. A trailing chip summarises the long-frame count. Hide it with
hideLegend.
Understanding the component
- Layout. Each frame renders as an
absolutebuttoninside a single track.leftis derived fromstart / windowMs,widthfromduration / windowMs, andtopfrom(maxDepth - depth) * rowHeightPxso depth 0 is the bottom row and the stack grows up. The chart inferswindowMsandmaxDepthfrom the frames when the props are omitted. - Hover detail. Each frame is a focusable button.
mouseenterandfocuslight up the detail panel;mouseleaveandblurput it back. The detail panel doubles as thearia-describedbytarget for the chart while a frame is active. - Threshold.
longTaskThresholdMsdefaults to50— the Long Tasks API definition. Override it for stricter budgets (e.g.16for a frame budget) or to demo the effect of shrinking the threshold. - Motion. Each block fades and
scaleX-springs in once on mount (springsnap). The animation short-circuits underprefers-reduced-motion.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
frames | FlameChartFrame[] | required | Function-call frames to render. |
title | ReactNode | — | Optional heading above the chart. |
description | ReactNode | — | Optional sub-headline under the title. |
windowMs | number | inferred | Total width of the chart in ms. |
longTaskThresholdMs | number | 50 | Threshold above which a frame reads as long. |
rowHeightPx | number | 22 | Height of each call-stack row in pixels. |
hideLegend | boolean | false | Hide the legend chip in the footer. |
hideRuler | boolean | false | Hide the time ruler across the top. |
headingAs | 'h2' | 'h3' | 'h4' | 'h3' | Tag for the title element. |
className | string | — | Merged onto the root via cn(). |
FlameChartFrame
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier. |
label | ReactNode | Function or task label rendered inside the block and detail panel. |
depth | number | Call-stack row, zero-indexed from the bottom. |
start | number | Offset in ms from the left edge of the chart. |
duration | number | Length of the frame in ms. |
color | string | Optional CSS background colour override. |
detail | ReactNode | Optional richer body for the hover detail panel. |
Accessibility
- The wrapper is a
<section>withdata-cb-edu="flame-chart". The track is arole="img"with anaria-labelsummarising the frame count, window width, and long-frame verdict. - Each frame is a focusable
<button>with a descriptivearia-label. Keyboard users can tab through every frame and read the detail panel. - The detail panel is a
role="status"aria-live="polite"region, and is wired asaria-describedbyon the track whenever a frame is active. - Each block exposes
data-cb-verdict="long" | "short"so consumers can extend the palette without monkey-patching CSS. - Animations are limited to the block enter and short-circuit under
prefers-reduced-motion.
Credits
- Extracted from:
terminal-dreams(src/components/frontend-design/perf-javascript/ui/FlameChart.tsx). The original was wired to a project-leveljs-perf-simulatorengine, hard-coded a "Main" / "Worker" lane pair, and rendered TTI / first-paint / click-event markers from a project script id. This rewrite drops every project-specific prop and the simulator chrome — consumers pass their ownframesarray withdepth, so the chart visualises any call stack, not just the JS-perf lesson timeline.