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.jsonUsage
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 thecb-labelstyle) anddescription. 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
- Baseline vs hinted. The dashed bar uses
start..start+durationfrom each resource. The accent bar uses the same duration but shifts the start earlier bysavingsFor(hint, duration). The shift is capped to 80% of the row duration so the bar never inverts. - 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. - Total span. When
totalMsis omitted the widget pads the latest baseline end by 15% and rounds up to the nearest 100ms. - Motion. Each bar grows from zero width with
SPRINGS.smooth. Animations short-circuit underprefers-reduced-motion.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
resources | HintsTimelineResource[] | required | Ordered list of resources on the timeline. |
title | ReactNode | — | Optional heading above the panel. |
description | ReactNode | — | Optional sub-headline under the title. |
totalMs | number | derived | Total timeline span in milliseconds. |
savingsFor | (hint, duration) => number | per-hint table | Function returning the ms saved by hint for a row of duration. |
hideBaseline | boolean | false | Hide the dashed baseline span on hinted rows. |
hideHintBadge | boolean | false | Hide the trailing hint badge. |
headingAs | 'h2' | 'h3' | 'h4' | 'h3' | Tag for the title element. |
baselineLabel | ReactNode | 'baseline' | Label printed alongside the baseline page-end. |
withHintsLabel | ReactNode | 'with hints' | Label printed alongside the with-hints page-end. |
className | string | — | Merged onto the root via cn(). |
HintsTimelineResource
| Field | Type | Description |
|---|---|---|
id | string | Stable identifier. |
label | ReactNode | Visible row label. |
hint | 'preload' | 'preconnect' | 'prefetch' | 'dns-prefetch' | 'none' | Hint bucket; drives the trailing badge and the with-hints shift. |
start | number | Baseline start time in milliseconds. |
duration | number | Baseline duration in milliseconds. |
Accessibility
- The wrapper is a
<section>withdata-cb-edu="hints-timeline"anddata-cb-has-hints="true | false"mirroring whether the with-hints end is earlier than the baseline. - The timeline rail carries an
aria-labeldescribing it as the resource-hints timeline; the bars arearia-hiddenbecause their meaning is conveyed in the per-rowaria-labeland the summary strip. - Every row carries an
aria-labelof 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 thehints-simulatorengine —Barrecords carryingconnectionPhases(DNS/TCP/TLS sub-bars),kind(document/css/js/module/font/image/third-party),priority('high'/'medium'/'low'/'idle'),forNextNav,hasParseOverlay, andhintsAppliedarrays 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 pluggablesavingsFormodel.