Hints Timeline

A baseline vs. with-hints timeline for resource hints. Pass a flat list of resources tagged with start, duration, and an optional hint (one of 'preload', 'preconnect', 'prefetch', 'dns-prefetch', or 'none'). Each row draws a dashed baseline span at the original timing and a solid accent span shifted earlier by the modelled saving. A summary footer prints the page-end time for both scenarios and the absolute difference.

Preview

Resource hints

Dashed = baseline timing, solid accent = with-hints timing.

document
app.css
Inter.woff2
hero.jpg
next-page.js
analytics.js
Page endbaseline 1.2s · with hints 1.1s · −140ms
Customize
Baseline start times (ms)
260ms
380ms
420ms
Hint savings (ms)
220ms
180ms
Display

Installation

npx shadcn@latest add https://craftbits.dev/r/hints-timeline.json

Usage

import {
  HintsTimeline,
  type HintsTimelineResource,
} from "@craft-bits/core";
 
const resources: HintsTimelineResource[] = [
  { id: "doc", label: "document", start: 0, duration: 240 },
  { id: "app-css", label: "app.css", hint: "preload", start: 260, duration: 360 },
  { id: "font", label: "Inter.woff2", hint: "preconnect", start: 380, duration: 320 },
  { id: "hero", label: "hero.jpg", hint: "preload", start: 420, duration: 480 },
  { id: "next", label: "next-page.js", hint: "prefetch", start: 900, duration: 320 },
];
 
export function Demo() {
  return (
    <HintsTimeline
      title="Resource hints"
      resources={resources}
    />
  );
}

Pass savingsFor to plug in your own model — e.g. derive savings from a profiler trace or a per-hint configuration — and the widget will recompute every row's hinted span on the fly.

Anatomy

  • Header. Optional title (rendered with the cb-label style) and description. Omit both for a chromeless panel.
  • Timeline rail. A stack of resource rows. Each row carries a leading label, a horizontal rail with the dashed baseline bar and the solid with-hints bar stacked above and below the same axis, and a trailing hint badge ('preload', 'preconnect', 'prefetch', 'dns-prefetch', or '—').
  • Summary strip. Beneath the rail — prints the baseline page end, the with-hints page end, and the savings (the absolute difference). Tints accent when savings are positive.

Understanding the component

  1. Baseline vs hinted. The dashed bar uses start..start+duration from each resource. The accent bar uses the same duration but shifts the start earlier by savingsFor(hint, duration). The shift is capped to 80% of the row duration so the bar never inverts.
  2. Default savings table. Preload saves 220ms, preconnect 180ms, prefetch 140ms, and dns-prefetch 60ms — each clamped to the cap. Replace the entire model by passing savingsFor.
  3. Total span. When totalMs is omitted the widget pads the latest baseline end by 15% and rounds up to the nearest 100ms.
  4. Motion. Each bar grows from zero width with SPRINGS.smooth. Animations short-circuit under prefers-reduced-motion.

Props

PropTypeDefaultDescription
resourcesHintsTimelineResource[]requiredOrdered list of resources on the timeline.
titleReactNodeOptional heading above the panel.
descriptionReactNodeOptional sub-headline under the title.
totalMsnumberderivedTotal timeline span in milliseconds.
savingsFor(hint, duration) => numberper-hint tableFunction returning the ms saved by hint for a row of duration.
hideBaselinebooleanfalseHide the dashed baseline span on hinted rows.
hideHintBadgebooleanfalseHide the trailing hint badge.
headingAs'h2' | 'h3' | 'h4''h3'Tag for the title element.
baselineLabelReactNode'baseline'Label printed alongside the baseline page-end.
withHintsLabelReactNode'with hints'Label printed alongside the with-hints page-end.
classNamestringMerged onto the root via cn().

HintsTimelineResource

FieldTypeDescription
idstringStable identifier.
labelReactNodeVisible row label.
hint'preload' | 'preconnect' | 'prefetch' | 'dns-prefetch' | 'none'Hint bucket; drives the trailing badge and the with-hints shift.
startnumberBaseline start time in milliseconds.
durationnumberBaseline duration in milliseconds.

Accessibility

  • The wrapper is a <section> with data-cb-edu="hints-timeline" and data-cb-has-hints="true | false" mirroring whether the with-hints end is earlier than the baseline.
  • The timeline rail carries an aria-label describing it as the resource-hints timeline; the bars are aria-hidden because their meaning is conveyed in the per-row aria-label and the summary strip.
  • Every row carries an aria-label of the form "app.css preload 40ms to 400ms, baseline 260ms to 620ms" so screen readers convey the label, the hint, the hinted span, and the baseline span without depending on colour.
  • The summary strip lives in an aria-live="polite" region so the deltas update audibly when the consumer mutates the resource list.
  • Animations short-circuit under prefers-reduced-motion.

Credits

  • Extracted from: terminal-dreams (src/components/frontend-design/perf-resource-hints/ui/HintsTimeline.tsx). The original was bound to the hints-simulator engine — Bar records carrying connectionPhases (DNS/TCP/TLS sub-bars), kind (document/css/js/module/font/image/third-party), priority ('high'/'medium'/'low'/'idle'), forNextNav, hasParseOverlay, and hintsApplied arrays computed from a fixed scenario graph. The original SVG hand-laid 96-pixel labels, 280-pixel bars, axis ticks every 250ms, a green page-load marker, ghost baseline outlines, and a five-swatch legend. This rewrite drops the simulator coupling and the SVG axis chrome, generalises the surface to resources: { id, label, hint?, start, duration }, and computes a single per-row baseline-vs-hinted bar pair with a pluggable savingsFor model.