Xzero UI
Pro library

TL;DR togglePro

An article card with a Full, Summary and Key points switch and a length slider. Switching streams an AI summary in place of the article as the card's height springs to fit, and each key point links back to the paragraph it came from, which gets scrolled to and highlighted.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui-pro/tldr-toggle
Live · keyboard ready

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui-pro/tldr-toggle

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { TldrToggle, type TldrSummarize } from "@/components/xzero/tldr-toggle/tldr-toggle";

// Stream from your route: newline-delimited JSON events, { type: "text", text } or { type: "point", source }.
const summarize: TldrSummarize = async function* ({ mode, length, targetWords, pointCount, article, signal }) {
  const response = await fetch("/api/tldr", {
    method: "POST",
    body: JSON.stringify({ mode, length, targetWords, pointCount, paragraphs: article.paragraphs }),
    signal,
  });
  const reader = response.body!.pipeThrough(new TextDecoderStream()).getReader();
  let buffer = "";
  for (;;) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += value;
    const lines = buffer.split("\n");
    buffer = lines.pop() ?? "";
    for (const line of lines) if (line) yield JSON.parse(line);
  }
};

export function PostSummary({ post }) {
  return (
    <TldrToggle
      article={{
        title: post.title,
        source: { name: "Engineering blog" },
        author: { name: post.author.name, avatar: post.author.avatar },
        date: post.date,
        paragraphs: post.paragraphs, // [{ id: "intro", text: "..." }, ...]
      }}
      summarize={summarize}
      model={{ name: "Claude Haiku 5.5", mark: <ClaudeMark style={{ color: "#D97757" }} /> }}
      onJump={id => track("tldr_jump", { id })}
    />
  );
}

API reference

TldrToggle

An article card with a Full, Summary and Key points switch and a length slider. Switching streams an AI summary in place of the article in fast 3 to 8 word chunks while the card's height follows on a spring with no bounce. Key points arrive as a checklist: each point ticks when it is written, and once the list is done its ¶ jump link switches back to the article, scrolls the source paragraph into view and highlights it. Results are kept per mode and length, so switching back is instant; Regenerate asks again, Stop or Escape ends a stream and keeps what arrived, and a failure shows the message with Try again. With no props it summarizes a sample Linear engineering post with a simulated Claude Haiku 5.5 and opens the summary by itself once in view.

PropTypeDefaultDescription
article{ title: string; paragraphs: { id: string; text: string }[]; source?: { name: string; mark?: ReactNode }; author?: { name: string; avatar?: string }; date?: string; readingTime?: string }tldrSampleArticleThe article to show and summarize. Paragraph ids are what key points link back to. readingTime is worked out at 230 words a minute when left out. An article with no paragraphs shows a quiet empty state.
summarize(request: TldrRequest) => AsyncIterable<TldrStreamEvent> | Promise<string | TldrKeyPoint[]>–Produces the result. The request carries mode ("summary" | "points"), length (1 to 5), targetWords (35 to 130), pointCount (3 to 7), the article and an AbortSignal that fires on Stop, Escape, a mode or length change, or unmount. Stream { type: "text", text } events for prose; for key points send { type: "point", source: paragraphId } before each point's text. A promise of the finished string or points also works: the card streams it in chunks itself. Leave it out to run the built-in simulation, which sends nothing.
model{ name: string; mark?: ReactNode }Claude Haiku 5.5The model credited in the footer, with a 14px mark. Give a mono mark its brand color, for example the Claude mark in #D97757.
mode"full" | "summary" | "points"–Controlled mode. Pair with onModeChange.
defaultMode"full" | "summary" | "points""full"Starting mode when uncontrolled.
onModeChange(mode: TldrMode) => void–Called when the switch, a jump link or autoplay changes the mode.
length1 | 2 | 3 | 4 | 5–Controlled length. Summaries aim for about 35, 60, 85, 100 and 130 words; key points return 3 to 7 items.
defaultLength1 | 2 | 3 | 4 | 52Starting length when uncontrolled.
onLengthChange(length: TldrLength) => void–Called when a slider drag is released or a key moves it, not on every step, so each change makes one request.
onComplete(result: { mode; length; text: string; points?: TldrKeyPoint[]; words: number }) => void–Called when a result finishes, for analytics or a server-side cache. Stopped and failed runs do not call it.
onJump(paragraphId: string) => void–Called when a key point's jump link is used, before the card scrolls to the paragraph.
autoPlaybooleantrue without summarizeOpens the summary by itself once the card is half in view for a second, unless the reader has already touched the card.
simulatedbooleantrue without summarizeAdds "Simulated, nothing was sent" to announcements and error text. Set it when you wrap simulateTldr yourself.
classNamestring–Class on the root element.

simulateTldr

The simulation behind the preview, exported for prototypes and tests. It waits briefly, then streams the authored summary or key points for the sample article (or lead sentences of any other article) in 3 to 8 word chunks every 30 to 40ms. Nothing is sent anywhere.

PropTypeDefaultDescription
request (required)TldrRequest–The request the card passes to summarize.
options.failbooleanfalseThrows after the thinking pause, to show the error state.
options.thinkMsnumber700Milliseconds of thinking before the first chunk.

streamResult

Turns a finished string or key point list into paced stream events, honoring the AbortSignal. Useful when a provider returns whole results but you still want the card to stream.

PropTypeDefaultDescription
result (required)string | TldrKeyPoint[]–The finished summary, or the key points with their source paragraph ids.
signal (required)AbortSignal–Stops the stream.

Keyboard interactions

KeysAction
TabMoves through the mode switch, the length slider, the key point jump links and the footer actions.
Arrow keysOn the mode switch, move between Full, Summary and Key points. On the slider, change the length by one step; each step makes one request.
Home / EndJump to the first or last mode, or the shortest or longest length.
Enter / SpaceOn a jump link, switch to the article, scroll to its source paragraph, move focus there and highlight it.
EscapeStops a running stream and keeps what arrived.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Also in ai

Guidance for AI tools

TL;DR toggle: An article card with a Full, Summary and Key points switch and a length slider. Switching streams an AI summary in place of the article as the card's height springs to fit, and each key point links back to the paragraph it came from, which gets scrolled to and highlighted. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components