Third Party Audit

A compact third-party script ledger — a totals strip (size and main-thread blocking, with a budget tick) and a sortable row list per script. Each row shows name, owner, transfer size, and a small bar of the blocking cost against the heaviest row. Drop it into a perf write-up to surface where the third-party time is going and who owns it.

Preview

Third-party scripts

Sorted by main-thread blocking. The heaviest row owns the budget.

Main-thread blocking550ms
target 200mspayload 291.4KB across 4 scripts
Sort by
  • Doubleclick Ads132.4KB
    RevenueDisplay ad delivery
    240ms
  • Google Analytics46.2KB
    MarketingPage-view + event tracking
    180ms
  • Intercom88.7KB
    SupportLive chat widget
    95ms
  • Twitter embed24.1KB
    EditorialTweet embeds in articles
    35ms
Trimming will helpThe top one or two rows dominate the cost. Talk to those owners about deferring or moving off the main thread.
Customize
Budget
200ms
Options

Installation

npx shadcn@latest add https://craftbits.dev/r/third-party-audit.json

Usage

import { ThirdPartyAudit } from "@craft-bits/core";
 
<ThirdPartyAudit
  scripts={[
    { id: "ga", name: "Google Analytics", owner: "Marketing", sizeKb: 46, blockingMs: 180 },
    { id: "ads", name: "Doubleclick Ads", owner: "Revenue", sizeKb: 132, blockingMs: 240 },
    { id: "intercom", name: "Intercom", owner: "Support", sizeKb: 89, blockingMs: 95 },
  ]}
/>

Anatomy

  • Header. Optional title (rendered with the cb-label style) and a description sub-line. Omit both for a chromeless panel.
  • Totals strip. Horizontal bar gauge of total main-thread blocking with a tick at the budget. Hide it with hideTotals.
  • Sort chips. Radio-group chips above the rows for blocking, size, owner, and name. Hide them with hideSort.
  • Row. Name and size on the top line, optional owner and purpose underneath, a per-row blocking bar sized against the heaviest script, and an optional richer detail line.
  • Verdict panel. A role="status" strip beneath the rows whose tone tracks the run-wide verdict (good / warn / bad).

Understanding the component

  1. Totals. On every render the component reduces scripts once into the totals (sum of blockingMs, sum of sizeKb, max of blockingMs). The verdict is good when the total is under goodBlockingMs, bad at or above badBlockingMs, and warn in between.
  2. Sortable rows. defaultSort seeds local state and the sort chip group flips it between blockingMs / sizeKb / owner / name. Sorting happens via a stable copy so the original scripts array is preserved.
  3. Per-row bar. Each row paints a small bar sized against maxBlockingMs. The heaviest row picks up data-cb-heaviest="true" so consumers can re-tone it without touching the component.
  4. Verdict styling. The root carries data-cb-verdict for the run-wide rating; the totals headline and verdict strip share the same hook. Override the colour by targeting [data-cb-edu="third-party-audit"][data-cb-verdict="bad"] in CSS.
  5. Motion. Each row fades and rises in once on mount (spring snap). The gauge fill and per-row bars use spring smooth. Every animation short-circuits under prefers-reduced-motion.

Props

PropTypeDefaultDescription
scriptsThirdPartyAuditScript[]requiredScripts to audit.
titleReactNodeOptional heading above the panel.
descriptionReactNodeOptional sub-headline under the title.
targetBlockingMsnumber200Budget tick painted on the gauge.
goodBlockingMsnumber200Total under this rates good.
badBlockingMsnumber600Total at or above this rates bad.
defaultSort'blockingMs' | 'sizeKb' | 'name' | 'owner''blockingMs'Initial sort key.
hideSortbooleanfalseHide the sort chip group.
hideTotalsbooleanfalseHide the totals strip.
headingAs'h2' | 'h3' | 'h4''h3'Tag for the title element.
classNamestringMerged onto the root via cn().

ThirdPartyAuditScript

FieldTypeDescription
idstringStable identifier, used as React key.
nameReactNodeHuman-readable script name.
sizeKbnumberTransfer size in kilobytes.
blockingMsnumberMain-thread blocking cost in milliseconds.
ownerReactNodeOwner / team responsible for the script.
purposeReactNodeOne-line purpose surfaced under the name.
detailReactNodeOptional richer body shown under the bar.

Accessibility

  • The wrapper is a <section> with data-cb-edu="third-party-audit" and aria-describedby wired to the totals strip whenever it is visible.
  • The row list is a role="list" with role="listitem" children — assistive tech reads it as a list of scripts.
  • The sort chips form a role="radiogroup" with role="radio" per chip; keyboard users get the native radio behaviour.
  • The verdict strip is a role="status" aria-live="polite" region so the rating updates as the sort or scripts change.
  • Reduced-motion users get static rows — no enter animation, no bar growth.

Credits

  • Extracted from: terminal-dreams (src/components/frontend-design/perf-other-assets/ui/ThirdPartyAudit.tsx). The original was wired to a project-level assets-perf-context engine, hard-coded a specific four-script stack, exposed per-script blocking / async / defer / partytown loading-mode chips, and rendered a Partytown markup snippet at the bottom. This rewrite drops the engine coupling, the loading-mode chrome, and the marketing copy — consumers pass a flat scripts array with sizeKb and blockingMs, and the primitive handles totals, sorting, and the impact verdict.