> ## Documentation Index
> Fetch the complete documentation index at: https://docs.context.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Generate a website from brand context

> Turn observed company branding into a small website theme, approved page content, and an editable responsive preview.

export const AgentSetupPrompt = ({children, variant = "setup"}) => {
  const isRecipe = variant === "recipe";
  const promptLabel = isRecipe ? "recipe prompt" : "setup prompt";
  const [copyState, setCopyState] = useState("idle");
  const contentRef = useRef(null);
  const detailsRef = useRef(null);
  const timerRef = useRef(null);
  const mountedRef = useRef(true);
  const pendingRef = useRef(false);
  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
      if (timerRef.current !== null) clearTimeout(timerRef.current);
    };
  }, []);
  const copyPrompt = useCallback(async () => {
    if (pendingRef.current) return;
    pendingRef.current = true;
    if (timerRef.current !== null) {
      clearTimeout(timerRef.current);
      timerRef.current = null;
    }
    setCopyState("copying");
    try {
      const code = contentRef.current?.querySelector("pre code") ?? contentRef.current?.querySelector("pre");
      const prompt = code?.textContent;
      if (typeof prompt !== "string" || !prompt.trim()) {
        throw new Error("Prompt is not available.");
      }
      if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
        throw new Error("Clipboard access is not available.");
      }
      await navigator.clipboard.writeText(prompt);
      if (!mountedRef.current) return;
      setCopyState("copied");
      timerRef.current = setTimeout(() => {
        if (mountedRef.current) setCopyState("idle");
        timerRef.current = null;
      }, 4000);
    } catch {
      if (!mountedRef.current) return;
      if (detailsRef.current) detailsRef.current.open = true;
      setCopyState("error");
    } finally {
      pendingRef.current = false;
    }
  }, []);
  const feedback = copyState === "copied" ? `${isRecipe ? "Recipe" : "Setup"} prompt copied. Paste it into your coding agent.` : copyState === "error" ? "Couldn't copy automatically. The prompt is open below. Select and copy the text manually." : "";
  return <section className="agent-setup-prompt not-prose" aria-label={`Agent ${promptLabel}`}>
      <style>{`
        .agent-setup-prompt {
          --asp-surface: oklch(0.985 0.005 255);
          --asp-border: oklch(0.9 0.012 255);
          --asp-title: oklch(0.25 0.025 255);
          --asp-text: oklch(0.45 0.021 255);
          --asp-accent: oklch(0.49 0.2 264);
          --asp-accent-hover: oklch(0.44 0.19 264);
          --asp-on-accent: oklch(0.985 0.005 255);
          --asp-focus: oklch(0.6 0.19 255);
          margin: 1.5rem 0;
          border: 1px solid var(--asp-border);
          border-radius: 0.875rem;
          background: var(--asp-surface);
          color: var(--asp-title);
          font-family: inherit;
          min-width: 0;
        }
        .dark .agent-setup-prompt {
          --asp-surface: oklch(0.2 0.014 255);
          --asp-border: oklch(0.33 0.016 255);
          --asp-title: oklch(0.96 0.006 255);
          --asp-text: oklch(0.78 0.013 255);
          --asp-accent: oklch(0.73 0.15 255);
          --asp-accent-hover: oklch(0.8 0.11 255);
          --asp-on-accent: oklch(0.19 0.025 255);
          --asp-focus: oklch(0.78 0.14 255);
        }
        .agent-setup-prompt__header { padding: 1.125rem 1.25rem; }
        .agent-setup-prompt__row {
          display: flex;
          align-items: flex-start;
          justify-content: space-between;
          gap: 1.25rem;
        }
        .agent-setup-prompt__intro { min-width: 0; }
        .agent-setup-prompt__title {
          margin: 0;
          font-size: 1.125rem;
          font-weight: 650;
          line-height: 1.4;
          color: var(--asp-title);
        }
        .agent-setup-prompt__subtitle {
          max-width: 65ch;
          margin: 0.375rem 0 0;
          font-size: 0.875rem;
          line-height: 1.6;
          color: var(--asp-text);
        }
        .agent-setup-prompt__copy {
          display: inline-flex;
          flex-shrink: 0;
          align-items: center;
          justify-content: center;
          gap: 0.5rem;
          min-height: 2.75rem;
          padding: 0.625rem 0.875rem;
          border: 1px solid transparent;
          border-radius: 0.5rem;
          background: var(--asp-accent);
          color: var(--asp-on-accent);
          font: inherit;
          font-size: 0.8125rem;
          font-weight: 600;
          line-height: 1.4;
          cursor: pointer;
        }
        .agent-setup-prompt__copy:hover:not(:disabled) {
          background: var(--asp-accent-hover);
        }
        .agent-setup-prompt__copy:disabled { cursor: wait; }
        .agent-setup-prompt__copy:focus-visible,
        .agent-setup-prompt__summary:focus-visible {
          outline: 2px solid var(--asp-focus);
          outline-offset: 3px;
        }
        .agent-setup-prompt__feedback {
          margin: 0.75rem 0 0;
          font-size: 0.8125rem;
          line-height: 1.5;
          color: var(--asp-text);
        }
        .agent-setup-prompt__feedback:empty { margin: 0; }
        .agent-setup-prompt__details { border-top: 1px solid var(--asp-border); }
        .agent-setup-prompt__summary {
          display: flex;
          align-items: center;
          gap: 0.5rem;
          padding: 0.75rem 1.25rem;
          border-radius: 0 0 0.875rem 0.875rem;
          font-size: 0.8125rem;
          font-weight: 500;
          line-height: 1.5;
          color: var(--asp-text);
          list-style: none;
          cursor: pointer;
        }
        .agent-setup-prompt__summary::-webkit-details-marker { display: none; }
        .agent-setup-prompt__summary:hover { color: var(--asp-title); }
        .agent-setup-prompt__details[open] .agent-setup-prompt__chevron {
          transform: rotate(90deg);
        }
        .agent-setup-prompt__content { min-width: 0; padding: 0 1.25rem 1rem; }
        .agent-setup-prompt__content > :first-child { margin-top: 0; }
        .agent-setup-prompt__content > :last-child { margin-bottom: 0; }
        .agent-setup-prompt__content pre {
          max-height: 32rem;
          overflow: auto;
          white-space: pre-wrap;
          overflow-wrap: anywhere;
        }
        @media (max-width: 640px) {
          .agent-setup-prompt__header { padding: 1rem; }
          .agent-setup-prompt__row { flex-direction: column; gap: 0.875rem; }
          .agent-setup-prompt__copy { width: 100%; }
          .agent-setup-prompt__summary { padding: 0.75rem 1rem; }
          .agent-setup-prompt__content { padding: 0 1rem 1rem; }
        }
      `}</style>

      <div className="agent-setup-prompt__header">
        <div className="agent-setup-prompt__row">
          <div className="agent-setup-prompt__intro">
            <h2 className="agent-setup-prompt__title">
              {isRecipe ? "Have your agent implement this recipe" : "Set up Context.dev"}
            </h2>
            <p className="agent-setup-prompt__subtitle">
              {isRecipe ? "Copy this prompt into your coding agent to build the recipe in your project." : "Paste into your coding agent. It will ask about your goal and project before making changes."}
            </p>
          </div>
          <button className="agent-setup-prompt__copy" type="button" onClick={copyPrompt} disabled={copyState === "copying"} aria-busy={copyState === "copying"}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" aria-hidden="true" focusable="false">
              {copyState === "copied" ? <path d="m5 12 4 4L19 6" strokeLinecap="round" strokeLinejoin="round" /> : <>
                  <rect x="8" y="8" width="12" height="12" rx="2" />
                  <path d="M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3" />
                </>}
            </svg>
            {copyState === "copied" ? "Copied" : copyState === "copying" ? "Copying…" : `Copy ${promptLabel}`}
          </button>
        </div>
        <p className="agent-setup-prompt__feedback" role="status" aria-live="polite" aria-atomic="true">
          {feedback}
        </p>
      </div>

      <details className="agent-setup-prompt__details" ref={detailsRef}>
        <summary className="agent-setup-prompt__summary">
          <svg className="agent-setup-prompt__chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" aria-hidden="true" focusable="false">
            <path d="m9 5 7 7-7 7" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
          {`View ${promptLabel}`}
        </summary>
        <div className="agent-setup-prompt__content" ref={contentRef}>
          {children}
        </div>
      </details>
    </section>;
};

<AgentSetupPrompt variant="recipe">
  ```text Recipe prompt theme={null}
  Implement this recipe in my project:
  https://docs.context.dev/use-cases/generate-branded-websites

  Read the recipe and linked API guides, inspect this project's stack, and build the complete flow using its existing conventions.

  Turn a verified company domain into an editable, responsive website preview. Map Brand and optional Styleguide, Screenshot, Markdown, and Fonts results into a small theme and validated page content. Handle optional source failures independently, use neutral fallbacks, and preserve saved content and theme overrides when regenerating. Keep source versions attached to generated drafts.

  Reuse existing Context.dev configuration and keep secret API keys on the server. Add focused tests, run the relevant checks, and document setup and how to try the result.
  ```
</AgentSetupPrompt>

Use a company's domain to seed a landing page, documentation site, or customer portal. Context.dev supplies brand assets and observed styles; your application maps them into components, renders a preview, and publishes the resulting site.

[Mintlify uses brand context when generating documentation sites](https://www.context.dev/blog/mintlify-turns-github-repos-into-branded-docs-sites-with-brand-dev). This recipe uses a small landing-page template whose colors and content can be edited before publication.

## Collect the inputs the template needs

Use a server-side key from the [Quickstart](/quickstart). Retrieve the [Brand profile](/guides/retrieve-brand-by-domain) for a verified domain, then add sources as needed:

| Source                                                   | Application decision                                         |
| -------------------------------------------------------- | ------------------------------------------------------------ |
| Brand                                                    | Suggested name, logo variants, and palette candidates        |
| [Styleguide](/guides/extract-design-system-from-website) | Typography, spacing, corners, and component references       |
| [Screenshot](/guides/take-webpage-screenshot)            | Visual reference for the reviewer or generation model        |
| [Markdown](/guides/scrape-websites-to-markdown)          | Source copy for supported descriptions and claims            |
| [Fonts](/api-reference/brand-intelligence/fonts)         | Font candidates to review for availability and permitted use |

A screenshot is reference material, not a code export. Handle optional calls independently and record their status. A failed screenshot should not discard a usable Brand profile or prevent a neutral preview.

Keep the source bundle on the server with its domain, retrieval time, and version. Return only the assets and tokens needed by the preview.

## Map observations into semantic roles

Do not copy every color into your component system. `brand.colors[]` distinguishes colors observed on the site from colors sampled from a logo. Choose roles that make sense for the new page, then review the result.

| Destination token        | Selection rule                                                            |
| ------------------------ | ------------------------------------------------------------------------- |
| Background and surface   | Neutral defaults unless a reviewed site treatment works for this template |
| Body text and muted text | Deliberate contrast against the chosen background                         |
| Accent                   | A reviewed site or brand color for buttons and small highlights           |
| Text on accent           | Black or white, selected by contrast                                      |
| Font family              | Approved available font, followed by a system fallback                    |
| Corner radius            | One bounded component value, not an arbitrary extracted CSS expression    |

The following adapter accepts a reviewed accent and preserves explicit editor choices. Its helper chooses black or white text by contrast against the accent.

```typescript site-theme.ts theme={null}
export type Theme = {
  background: string;
  text: string;
  accent: string;
  onAccent: string;
  fontStack: string;
  radius: number;
};

type ThemeOverrides = Partial<Pick<Theme, "accent" | "fontStack" | "radius">>;

function accentText(hex: string) {
  if (!/^#[0-9a-f]{6}$/i.test(hex)) throw new Error("Use a six-digit hex color");
  const rgb = [1, 3, 5].map((offset) => parseInt(hex.slice(offset, offset + 2), 16) / 255);
  const [r, g, b] = rgb.map((c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
  const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
  return (luminance + 0.05) / 0.05 >= 1.05 / (luminance + 0.05)
    ? "#000000" : "#ffffff";
}

export function makeTheme(
  approvedAccent: string | null,
  overrides: ThemeOverrides,
): Theme {
  const accent = overrides.accent ?? approvedAccent ?? "#334155";
  const radius = overrides.radius ?? 12;
  if (!Number.isFinite(radius) || radius < 0 || radius > 32) {
    throw new Error("Choose a radius between 0 and 32 pixels");
  }
  return {
    background: "#ffffff",
    text: "#171717",
    accent,
    onAccent: accentText(accent),
    fontStack: overrides.fontStack ?? "Arial, Helvetica, sans-serif",
    radius,
  };
}
```

Keep source candidates and overrides in separate records. Rebuilding the source bundle can propose a new accent; it should not erase the editor's choice. Create a new theme version whenever approved decisions change.

## Generate bounded page content

Ask a model for data your components can render, or let the editor fill it directly:

```json Example page content theme={null}
{
  "eyebrow": "Product overview",
  "headline": "One clear, supported product benefit",
  "description": "An approved description of the product.",
  "cta": { "label": "Explore the product", "url": "https://example.com/product" },
  "features": [
    { "title": "A real capability", "description": "A sourced explanation.", "sourceUrl": "https://example.com/product" }
  ]
}
```

Validate the object before rendering: cap text lengths and feature count, require a source for factual claims, and allow only approved HTTPS destinations. Use a fixed asset registry for logos and illustrations. Website content is source evidence, so instructions embedded in it must not control your generator's tools or publishing permissions.

## Render an editable preview

This React component uses the theme returned above and validated content. Keep the editor's fields outside the preview so edits update the typed model, not arbitrary HTML.

```tsx LandingPage.tsx theme={null}
import type { CSSProperties } from "react";
import type { Theme } from "./site-theme";

type PageContent = {
  eyebrow: string;
  headline: string;
  description: string;
  cta: { label: string; url: string };
  features: Array<{ title: string; description: string; sourceUrl: string }>;
};

export function LandingPage({ theme, content, companyName }: {
  theme: Theme;
  content: PageContent;
  companyName: string;
}) {
  const style = {
    "--accent": theme.accent,
    "--on-accent": theme.onAccent,
    "--radius": `${theme.radius}px`,
    background: theme.background,
    color: theme.text,
    fontFamily: theme.fontStack,
  } as CSSProperties;

  return (
    <div className="brand-page" style={style}>
      <header>{companyName}</header>
      <main>
        <p>{content.eyebrow}</p>
        <h1>{content.headline}</h1>
        <p>{content.description}</p>
        <a className="brand-cta" href={content.cta.url}>{content.cta.label}</a>
        <div className="brand-features">
          {content.features.map((feature) => (
            <section key={feature.title}>
              <h2>{feature.title}</h2>
              <p>{feature.description}</p>
            </section>
          ))}
        </div>
      </main>
    </div>
  );
}
```

The example uses the company name as its identity fallback; add a reviewed logo with dimensions and an image-error fallback when available.

```css landing-page.css theme={null}
.brand-page { padding: clamp(20px, 5vw, 64px); overflow-wrap: anywhere; }
.brand-page main { max-width: 1120px; margin: 64px auto; }
.brand-page h1 { max-width: 18ch; font-size: clamp(2rem, 5vw, 4rem); line-height: 1.1; }
.brand-page p { max-width: 65ch; line-height: 1.6; }
.brand-cta { display: inline-block; padding: 12px 20px; border-radius: var(--radius);
  color: var(--on-accent); background: var(--accent); }
.brand-cta:focus-visible { outline: 3px solid #171717; outline-offset: 4px; }
.brand-features { display: grid; gap: 24px; margin-top: 48px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); }
```

## Review and publish a version

Render the same template with two brand bundles and with no brand data. Check narrow and wide screens, keyboard focus, long content, blocked images, and unavailable fonts. Test the final page's text and control contrast; a readable button alone does not validate the entire page.

Save the content, approved assets, theme, and source-bundle version as one preview revision. Make publishing an explicit application action that selects that revision. A later source refresh should open a new draft without changing the live site or discarding manual edits.

<CardGroup cols={2}>
  <Card title="Extract a design system" icon="paintbrush" href="/guides/extract-design-system-from-website">
    Gather observed styles and understand their limits.
  </Card>

  <Card title="Theme an onboarding flow" icon="wand-magic-sparkles" href="/use-cases/faster-onboarding-flows">
    Apply editable branding to a new customer's workspace.
  </Card>
</CardGroup>
