Site
Use case how it works
Numbered implementation steps with install commands and code examples.
$ npx context-ds add use-case-how-it-worksUsage
Use it for
The "how it works" section of a use case or product page. UseCaseHowItWorks keeps the install command and the code on one language; UseCaseStepCards for three or four steps that each carry an example, with outline pills to the APIs under them.
Avoid
- Full syntax themes in the code (Context.dev's light highlighting only)
- More than four step cards in a row
- Steps without numbers
Dependencies: code-window, button, copy-button, lucide-react
Example
examples/use-case-how-it-works.tsx
import { SectionHeaderRule, SectionHeading, SiteSection } from "@/components/site/section";import { UseCaseHowItWorks, UseCaseStepCards } from "@/components/site/use-case-how-it-works";const TS = `import ContextDev from 'context.dev';const client = new ContextDev({ apiKey: process.env.CONTEXT_DEV_API_KEY });const crawl = await client.web.crawl({ url: 'https://docs.stripe.com', maxPages: 500, formats: { markdown: true },});for (const page of crawl.pages) { console.log(page.url, page.markdown.length);}`;const PY = `from context_dev import ContextDevclient = ContextDev(api_key=os.environ["CONTEXT_DEV_API_KEY"])crawl = client.web.crawl( url="https://docs.stripe.com", max_pages=500, formats={"markdown": True},)for page in crawl.pages: print(page.url, len(page.markdown))`;const CURL = `curl https://api.context.dev/v1/web/crawl \\ -H "Authorization: Bearer $CONTEXT_DEV_API_KEY" \\ -d '{ "url": "https://docs.stripe.com", "maxPages": 500 }'`;export function Site() { return ( <div className="flex w-full flex-col"> <SiteSection lines> <SectionHeading title="Crawl a site in" accent="three steps." description="Point it at a domain, choose the formats, and read every page back as clean Markdown." /> <SectionHeaderRule /> <UseCaseHowItWorks steps={[ { title: "Get an API key", description: "Sign up and copy your key from the dashboard." }, { title: "Start a crawl", description: "Send the start URL and how many pages to follow." }, { title: "Read the pages", description: "Each page comes back as Markdown, HTML or JSON." }, ]} samples={[ { language: "typescript", label: "TypeScript", filename: "crawl.ts", code: TS, install: "npm install context.dev" }, { language: "python", label: "Python", filename: "crawl.py", code: PY, install: "pip install context-dev" }, { language: "curl", label: "cURL", filename: "crawl.sh", code: CURL, install: null }, ]} docs={{ label: "Read the crawl docs", href: "https://docs.context.dev" }} note="Free to start: 1,000 credits a month, no card needed." /> </SiteSection> <SiteSection lines> <SectionHeading title="From URL to" accent="answer." /> <SectionHeaderRule /> <UseCaseStepCards steps={[ { title: "Search", description: "Find the pages that answer the question.", code: `const results = await client.web.search({\n query: 'stripe usage based billing',\n});` }, { title: "Scrape", description: "Read each result as Markdown.", code: `const page = await client.web.scrape({\n url: results[0].url,\n formats: { markdown: true },\n});` }, { title: "Answer", description: "Hand the pages to your model with their sources.", code: `const answer = await model.generate({\n context: page.markdown.data,\n});` }, ]} links={[ { label: "Search API", href: "https://context.dev/products/search" }, { label: "Scrape API", href: "https://context.dev/products/scrape" }, ]} /> </SiteSection> </div> );}Source
components/site/use-case-how-it-works.tsx
"use client";import * as React from "react";import { ArrowRight, Check, Copy } from "lucide-react";import { CodeBlock, TerminalWindow, WindowCopyButton } from "@/components/site/code-window";import { buttonVariants } from "@/components/ui/button";import { useCopy } from "@/components/ui/copy-button";import { cn } from "@/lib/utils";// The use case pages' "how it works": the numbered steps in a card on the left, with the install command for the// language that is picked, the docs pill and a free-tier note under them; the code in every SDK language in a tabbed// code window on the right. The install command and the code share one language. `UseCaseStepCards` is the other page// style: a card per step, each with its own example.const RING = "-m-2 rounded-card bg-ring-tint p-2";const CARD = "flex h-full min-w-0 flex-col gap-4 rounded-card bg-surface p-6 ring-1 ring-hairline md:p-8";export type UseCaseStep = { title: string; description: React.ReactNode };export type UseCaseSample = { language: string; label: string; code: string; filename: string; install?: string | null };function StepNumber({ n }: { n: number }) { return <span className="flex size-9 shrink-0 items-center justify-center rounded-pill bg-brand/10 text-body leading-snug text-fg-brand">{n}</span>;}/** The install command as an outlined pill with a copy button, or a note when the language needs no install. */function UseCaseInstallCommand({ sample, className }: { sample: UseCaseSample; className?: string }) { const { hasCopied, copy } = useCopy(); if (!sample.install) { return <p className={cn("w-fit max-w-full rounded-pill px-4 py-2 text-body-sm leading-snug text-fg-secondary ring-1 ring-black/10 ring-inset", className)}>No install needed. Call the REST API directly.</p>; } return ( <div className={cn("flex w-fit max-w-full items-center gap-2 rounded-pill py-1 pr-1 pl-4 ring-1 ring-black/10 ring-inset", className)}> <code className="min-w-0 overflow-x-auto font-mono text-body-sm leading-5 whitespace-nowrap text-fg-nav [scrollbar-width:none]"> <span aria-hidden="true" className="text-black/35"> ${" "} </span> {sample.install} </code> <button type="button" onClick={() => copy(sample.install ?? "")} aria-label={hasCopied ? `${sample.label} install command copied` : `Copy ${sample.label} install command`} className="flex size-8 shrink-0 items-center justify-center rounded-pill text-black/45 outline-none transition-[background-color,color] duration-150 ease-out hover:bg-tint hover:text-fg-brand focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring motion-reduce:transition-none" > {hasCopied ? <Check aria-hidden="true" className="size-4 text-fg-brand" /> : <Copy aria-hidden="true" className="size-4" />} </button> </div> );}/** The tabbed code window: the file name as its label, a pill tab per language, a fixed height that scrolls. */function UseCaseCodeTabs({ samples, language, onLanguageChange, className }: { samples: readonly UseCaseSample[]; language: string; onLanguageChange: (language: string) => void; className?: string }) { const baseId = React.useId(); const active = samples.find((sample) => sample.language === language) ?? samples[0]; if (!active) return null; const onKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => { const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0; if (!step) return; event.preventDefault(); const next = samples[(samples.indexOf(active) + step + samples.length) % samples.length]; onLanguageChange(next.language); document.getElementById(`${baseId}-tab-${next.language}`)?.focus(); }; return ( <div className={cn(RING, "flex min-w-0 flex-col", className)}> <TerminalWindow className="flex flex-1 flex-col" label={active.filename} actions={<WindowCopyButton value={active.code} label={`Copy ${active.label} code`} />}> <div role="tablist" aria-label="Code language" className="flex min-w-0 gap-1 overflow-x-auto border-b border-white/10 px-3 py-2 [scrollbar-width:none]"> {samples.map((sample) => { const selected = sample === active; return ( <button key={sample.language} id={`${baseId}-tab-${sample.language}`} type="button" role="tab" aria-selected={selected} aria-controls={`${baseId}-panel-${sample.language}`} tabIndex={selected ? 0 : -1} onClick={() => onLanguageChange(sample.language)} onKeyDown={onKeyDown} className={cn( "shrink-0 rounded-pill px-3 py-1.5 text-body-sm leading-snug outline-none transition-[background-color,color] duration-150 ease-out focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-white motion-reduce:transition-none", selected ? "bg-white/10 text-white" : "text-white/55 hover:text-white", )} > {sample.label} </button> ); })} </div> <div className="relative h-88 lg:h-auto lg:min-h-88 lg:flex-1"> {samples.map((sample) => ( <div key={sample.language} id={`${baseId}-panel-${sample.language}`} role="tabpanel" aria-labelledby={`${baseId}-tab-${sample.language}`} tabIndex={0} hidden={sample !== active} className="absolute inset-0 overflow-auto outline-none [scrollbar-width:thin] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus-ring" > <CodeBlock code={sample.code} /> </div> ))} </div> </TerminalWindow> </div> );}/** * The how it works block: the steps card (24rem wide from lg) and the code tabs beside it. `docs` is the outline pill * under the steps; `note` the muted line under that (the free tier, for example). */function UseCaseHowItWorks({ steps, samples, docs, note, defaultLanguage, className,}: { steps: readonly UseCaseStep[]; samples: readonly UseCaseSample[]; docs?: { label: string; href: string }; note?: React.ReactNode; defaultLanguage?: string; className?: string;}) { const [language, setLanguage] = React.useState(defaultLanguage ?? samples[0]?.language ?? ""); const active = samples.find((sample) => sample.language === language) ?? samples[0]; return ( <div data-slot="use-case-how-it-works" className={cn("grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,24rem)_minmax(0,1fr)]", className)}> <div className={cn(RING, "min-w-0")}> <div className={cn(CARD, "gap-6")}> <ol className="flex flex-col"> {steps.map((step, i) => ( <li key={step.title} className="flex items-start gap-4 border-t border-hairline py-4 first:border-0 first:pt-0"> <StepNumber n={i + 1} /> <span className="flex flex-col gap-1"> <span className="text-h5 leading-snug font-normal text-fg">{step.title}</span> <span className="text-body-sm leading-snug text-fg-secondary">{step.description}</span> </span> </li> ))} </ol> {(active || docs || note) && ( <div className="mt-auto flex flex-col items-start gap-4 border-t border-hairline pt-6"> {active && <UseCaseInstallCommand sample={active} />} {docs && ( <a href={docs.href} target="_blank" rel="noopener noreferrer" className={buttonVariants({ variant: "pill-outline" })}> {docs.label} </a> )} {note && <p className="text-body-sm leading-snug text-fg-subtle">{note}</p>} </div> )} </div> </div> {samples.length > 0 && <UseCaseCodeTabs samples={samples} language={active?.language ?? ""} onLanguageChange={setLanguage} />} </div> );}export type UseCaseStepWithCode = UseCaseStep & { code: string };/** * A card per step, three across (four when there are four): the number, the title, a sentence and the step's code. In a * row, the longest example scrolls at the height of the next longest, so one long example does not stretch every card. * `links` are outline pills to the APIs the workflow uses. */function UseCaseStepCards({ steps, links, className }: { steps: readonly UseCaseStepWithCode[]; links?: readonly { label: string; href: string }[]; className?: string }) { const visibleLines = steps.map((step) => step.code.split("\n").length).sort((a, b) => b - a)[1] ?? 12; return ( <div data-slot="use-case-step-cards" className={className}> <ol className={cn("grid grid-cols-1 gap-6", steps.length === 4 ? "lg:grid-cols-4" : "lg:grid-cols-3")}> {steps.map((step, i) => ( <li key={step.title} className={RING}> <div className={CARD}> <div className="flex flex-col gap-4"> <StepNumber n={i + 1} /> <h3 className="text-h4 leading-snug font-normal tracking-normal text-fg">{step.title}</h3> <p className="text-body leading-snug text-fg-secondary">{step.description}</p> </div> <div tabIndex={0} role="region" aria-label={`${step.title} code example`} style={{ "--visible-code-lines": visibleLines } as React.CSSProperties} className="overflow-x-auto rounded-card bg-code outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring lg:max-h-[calc(var(--visible-code-lines)*1.7em+2.5rem)] lg:overflow-y-auto" > <CodeBlock code={step.code} /> </div> </div> </li> ))} </ol> {links && links.length > 0 && ( <nav aria-label="APIs for this workflow" className="mt-10 flex flex-wrap gap-3"> {links.map((link) => ( <a key={link.href} href={link.href} className={cn(buttonVariants({ variant: "pill-outline" }), "gap-2 py-2.5")}> {link.label} <ArrowRight aria-hidden="true" className="size-4" /> </a> ))} </nav> )} </div> );}export { UseCaseHowItWorks, UseCaseCodeTabs, UseCaseInstallCommand, UseCaseStepCards };Reference: src/components/sections/use-cases/use-case-sections.tsx; src/components/sections/use-cases/use-case-code-tabs.tsx; src/components/sections/usecases/use-case-landing-page.tsx.