Customize

Customize

Colors, fonts, themes, content — the full customization playbook.

Sourcedocs/CUSTOMIZE.md

This guide shows how to make AgentKit look and behave like YOUR product, not the demo. Everything the page says lives in one file, site.config.ts. Colors and fonts are CSS (Step 2).


Step 0 · Rebrand with your AI (5 minutes)

The fastest route: let your coding assistant fill in the config.

  1. Open this project in Cursor, Claude Code, or any assistant that can edit files.
  2. Open prompts/rebrand.md and paste the whole file into the assistant.
  3. Replace the brief at the bottom with your own: what the product is, who it is for, the main job it does, how it is priced, your links, and any real quotes or facts you have permission to use.
  4. Review the diff of site.config.ts before you accept it. Check the pricing, the claims, and the demo run: they must be true of your product.

The assistant also reads AGENTS.md (and .cursor/rules/agentkit.mdc in Cursor), which tell it the rules of this project: copy goes in the config, never invent social proof, run the checks before finishing. Prefer to do it by hand? Steps 1 to 4 walk through the same file.


Step 1 · Edit site.config.ts (20 minutes)

site.config.ts ships filled in with an example product called Relay. Replace each group of fields with yours. Its types and rules are in src/config/schema.ts; copy marked "inline" there supports *em* (brand-colored italic), **strong** and `code`.

  • product, seo, og, llms: the product name, one-line description, production URL (no trailing slash), logo text, organization, primary call to action, search title and descriptions, the text on the share image, and the summary served at /llms.txt. Set product.url to your real domain: canonical links, the sitemap and structured data are built from it.

  • hero: badge, headline, subhead, both CTAs, the note under them and the metric strip. Wrap one or two words of the headline in *…* to get the italic accent:

    hero: {
      headline: 'Get paged with *the answer*, not the alert.',
      primaryCta: { label: 'Start free →', href: '#pricing' },
      // ...
    },
    
  • features, agentTrace, toolCalling: label, heading, body and bullets for the three product-explainer sections. The animated content inside them comes from demo (Step 3).

  • pricing: plans is a list. One plan renders a plain table; two or more render a toggle using each plan's tabLabel. Every tier has a name, price, tagline, features and a CTA; mark one featured: true.

  • faq: the questions. Each has a unique id; defaultOpen names the id that starts expanded.

  • finalCta and footer: the closing card, link columns, copyright and bottom line.

  • nav: the header links.

Anything that does not fit fails loudly, naming the field:

site.config: hero.primaryCta.href must start with https://, http://, mailto:, # or / (got "relay.com")

The same validator runs on bun run dev, bun run build and bun run test, so a typo cannot reach production. For the headline formulas that work for AI products, see docs/COPY-FRAMEWORK.md.


Step 2 · Brand color and fonts (6 minutes)

Brand accent color (1 minute)

Every acid-green touch on the page comes from a single CSS variable. Edit src/app/globals.css:

:root, [data-theme="editorial-dark"] {
  --color-brand: #c6f432;         /* ← change this */
  --color-brand-tint-weak: rgba(198, 244, 50, 0.02);
  --color-brand-tint: rgba(198, 244, 50, 0.1);
  --color-brand-tint-strong: rgba(198, 244, 50, 0.25);
}

Use a hex-to-rgba converter to update the three tint variants. That's all: every component re-tints automatically.

Pro tip: run your brand color through Coolors to ensure it has enough contrast on the dark background (aim for at least 4.5:1 for text, 3:1 for large display text).

Swap fonts (5 minutes)

AgentKit uses three fontsource packages. To replace them:

# Remove defaults
bun remove @fontsource-variable/inter @fontsource/instrument-serif @fontsource-variable/jetbrains-mono

# Install your fonts (examples)
bun add @fontsource-variable/geist @fontsource/playfair-display @fontsource-variable/geist-mono

Update src/app/globals.css:

@import "@fontsource-variable/geist";
@import "@fontsource/playfair-display";
@import "@fontsource-variable/geist-mono";

@theme {
  --font-display: 'Geist Variable', sans-serif;
  --font-serif:   'Playfair Display', Georgia, serif;
  --font-mono:    'Geist Mono Variable', monospace;
}

Update src/app/layout.tsx preload tags to point at the new font files.

Favicon (1 minute)

Replace public/favicon.svg with your own mark (keep the file name, or change the path in src/config/seo.ts). The agentkit-theme key in public/theme-init.js only names the visitor's saved theme in localStorage; visitors never see it, so renaming it is optional.


Step 3 · Your demo and your model (15 minutes)

The scripted demo

Visitors who reach the page without a live model still see the product working. Three scripts in site.config.ts drive it, all under demo:

  • demo.heroChat: the exchange that auto-plays in the hero chat (a user message, then an assistant reply).
  • demo.heroRun (optional): a whole scripted agent run for the hero instead: reasoning, tool steps, an approval that approves itself, the reply and usage figures. Without it the hero plays demo.heroChat.
  • demo.trace: the steps in the agent trace (thought, tool call, tool call, thought, final).
  • demo.toolCall: the single tool call walked through in the tool-calling section, with its result rows.

Rewrite them as one realistic run of your product doing its main job. The streaming card in the features section replays text from streamingDemoChunks in src/lib/demo-agent.ts.

Live chat: provider and model

The ai block picks what answers real visitors:

ai: {
  provider: 'openai',        // 'openai', 'anthropic' or 'openrouter'
  model: 'gpt-6-luna',       // must be a model of that provider in src/lib/models.ts
  systemPrompt: 'You are Relay, an on-call agent. Be concise. ...',
  // Everything below is optional:
  allowedModels: ['gpt-6-luna'],   // models the browser may request; default [model, ...fallbackModels]
  fallbackModels: [],              // tried in order if the model fails before it starts streaming
  reasoning: 'medium',             // 'low' | 'medium' | 'high'
  maxSteps: 6,                     // model calls per run, 1 to 10
  maxOutputTokens: 1500,           // output tokens per model call, 64 to 8192
  allowClientSystemPrompt: false,  // true lets the browser send its own system prompt (local development only)
},

Put the matching key in .env.local: OPENAI_API_KEY for OpenAI, ANTHROPIC_API_KEY for Anthropic, OPENROUTER_API_KEY for OpenRouter (one key reaches hundreds of models; use OpenRouter model ids from src/lib/models.ts). To try another model without editing the config, set AI_PROVIDER and AI_MODEL in the environment; they override ai. With no usable setup (no key, unknown model) the hero stays a scripted demo, and POST /api/chat answers 503 with the reason, such as OPENAI_API_KEY is not set. Visitors see the scripted demo.

What the browser may send to /api/chat: the conversation (messages), an optional model (it must be in allowedModels, otherwise the route answers 400) and an optional temperature (clamped to 0 to 1). The system prompt always comes from ai.systemPrompt unless allowClientSystemPrompt is true; the starter's system-prompt editor is read-only otherwise. Fallback models are tried only before the first streamed byte: a 429 is not retried, and if every model fails the route answers 503 and the hero plays its scripted demo. The hero header and run footer show the model that actually answered.

In production the live chat also needs Upstash Redis (UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN) for rate limiting; without it the route fails closed rather than exposing an unbounded model endpoint. The per-IP limiter is not a budget: read Cost of a public agent and set a spend limit at your provider before you launch.

Replace the demo tools with YOUR tools

The agent's tools live in src/lib/demo-tools.ts. It ships three demo tools: list_deploys and search_logs (read-only, they run without approval) and rollback_deploy (changes production, so it needs approval). All three return canned data. Replace them in that file; the route in src/app/api/chat/route.ts picks them up:

export const demoTools = {
  // Remove list_deploys, search_logs and rollback_deploy

  // Add YOUR agent's real tools
  your_tool_name: tool({
    description: 'What this tool does, for the model',
    inputSchema: z.object({
      arg1: z.string().describe('Meaning for the model'),
      arg2: z.number().optional(),
    }),
    execute: async ({ arg1, arg2 }) => {
      // Call your backend, database, external API, etc.
      return await yourBackendCall(arg1, arg2)
    },
  }),
},

The chat UI updates automatically; no front-end changes needed.

Which tools need approval. Any tool that changes state (writes, deletes, sends, charges, deploys) must require approval. List it in demoToolApproval in the same file:

export const demoToolApproval = {
  your_tool_name: 'user-approval',
} as const

The route then streams an approval request instead of running the tool, the hero shows an Approve / Deny card, and the tool runs only if the visitor approves. If they deny, the agent carries on and says it did not run the action. Read-only tools can skip the list.

Approvals in production. Set TOOL_APPROVAL_SECRET (any long random string, the same on every server instance, e.g. openssl rand -hex 32). It signs approval requests so a forged or tampered approval is rejected with 400. In production the route logs a warning when approval-gated tools exist and the secret is unset. Signing does not stop a genuine signed approval from being replayed, so a tool with a real side effect also needs server-side idempotency or persisted approval state (for example a job id the tool checks before it acts). The demo tools change nothing, so they need neither.

Approval is not authorization. The chat endpoint is public. Approval asks the person chatting; it does not prove who they are. A tool that touches a user's or your systems' data must authenticate the request (session/cookie) and authorize the action inside execute.

The scripted demo: demo.heroRun

On page load the hero always autoplays the run in demo.heroRun in site.config.ts, whether or not a live model is set up. The run has a prompt, the reasoning text, tool steps, one approval (the gated tool, its args and a description), the reply and the metadata shown in the run footer (steps, tokens, cached tokens, cost). It is always labelled "scripted demo" and its approval card is disabled ("Scripted demo — approves itself"). Without heroRun the hero plays the older two-message demo.heroChat. The moment the visitor types, the real model takes over (when one is set up). Write a run for your own product so the scripted version shows what your agent does.


Step 4 · Social proof (the honesty step)

socialProof in site.config.ts has two lists: proof (verifiable facts) and testimonials (real quotes). Both ship empty, and while both are empty the whole section is hidden. Keep it that way until you have something true to put in it. Visitors can smell invented proof immediately.

Option A: Get real testimonials before launch.

  • Reach out to 3-5 beta users. Offer them the product free for 3 months in exchange for a short quote.
  • Ask for something specific ("What did we let you ship faster?"), not generic praise.
  • Add each as a testimonials entry, with the person's permission.

Option B: Leave it empty.

  • Better to have no testimonials than fake ones. Do nothing; the section does not render.

Option C: Use different proof.

  • GitHub stars, download counts, usage metrics and press mentions go in proof, but only numbers you can verify. Never round up.

Step 5 · Remove or reorder sections

Not every AI product needs a pricing section (some sell contracts only). To remove or reorder, edit src/app/page.tsx: it stacks the sections as JSX elements (some span a few lines). To remove a section, delete its element and its import; to reorder, move the elements.

After removing a section, update the links that pointed at it: nav.links, the footer groups, and any CTA whose href is the section's anchor (#pricing, #faq, and so on) in site.config.ts. An anchor with no target scrolls nowhere.


Step 6 · Theme switcher (optional removal)

src/components/theme-switcher.tsx lets visitors toggle between the three built-in themes. Remove it once you pick your production theme:

  1. In src/components/nav.tsx, delete the <ThemeSwitcher /> element.
  2. In src/app/layout.tsx, replace data-theme="editorial-dark" with your chosen theme.
  3. Delete src/components/theme-switcher.tsx.
  4. Delete the 2 non-production themes from src/app/globals.css if you want a smaller CSS file.

Step 7 · Add your own section (optional)

AGENTS.md has the checklist under "Adding a section". In short:

  1. Create src/components/sections/your-section.tsx exporting a component that takes { content }, typed by a new YourSectionContent type in src/config/schema.ts. Sections never import the config themselves; only pages, layouts and routes do.
  2. Add the field to SiteConfig and fill it in site.config.ts.
  3. Render it in src/app/page.tsx with content={site.yourSection}.
  4. Give its <section> an id if anything links to it.

Match the layout of the existing sections (max-w-[1200px] mx-auto px-6 py-24 md:py-32), the typography scale (H2 = 40-52px), and use CSS vars for colors.


Step 8 · Before you deploy: showcase: false

showcase: true (the default) keeps the kit's own catalog on your site: /components, /sections, /docs and /starters. They are handy while you build and are how you browse what you bought, but they are not your product.

Before you deploy, set it to false:

showcase: false,

With it off, those routes return 404 (src/proxy.ts answers every catalog request before anything renders, and the build no longer prerenders those pages; if you add your own proxy logic, keep that check), the sitemap and /llms.txt stop listing them, and the nav, footer and final-CTA links that point at them are dropped. The validator also rejects any catalog link left in hero.primaryCta, hero.secondaryCta, finalCta.primaryCta, pricing.note.link, any pricing tier cta, or any socialProof.contact.links entry, because those would ship as dead links. Fix what it names, run bun run build, and you are ready to deploy.


Questions?

Email me. Actual human.

— webdesignhot.com