# Consistent Title Format and a Written Description

**MUST** · **ID:** `design-meta-title-and-description` · **Category:** design
**Source:** [@Ibelick](https://www.ui-skills.com/)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/design-meta-title-and-description

> MUST: Every page needs a `<title>` in one site-wide format (`${page} — ${SITE_NAME}`), with the distinctive word FIRST — results truncate around 60 characters, so a stuffed title loses the product name, the one word a human was scanning for. Shareable or searchable pages SHOULD also ship a hand-written `<meta name="description">` of ~150-160 plain-text characters; skip it and the engine writes one from whatever body copy came first, usually a cookie banner or a skip link. (Distinct from `content-page-titles`, which is the title tracking in-app context.)

Every page needs a title in one site-wide format, short enough to survive truncation, plus a description you wrote instead of one the crawler invents

From the title and description rules in ibelick's fixing-metadata skill. Distinct from content-page-titles, which is about the title tracking in-app context as the user navigates; this is the site-wide template and the description that ships with the document. Three things go wrong together. Titles get stuffed — `Home | Best Free AI Tools 2026 | Fast | Cheap | MyApp` — and the search result truncates at roughly 60 characters, so the keywords survive and the product name, the one word a human was scanning for, is the part that gets cut. Titles drift, because each page invents its own separator and word order, and a site whose results do not look like they came from the same site reads as untrustworthy. And the description is skipped, which does not mean no description: it means the engine writes one for you, scraped from whatever body copy came first, which is usually a cookie banner or a skip link. The rule reduces to one template (`${page} — ${SITE_NAME}`), the distinctive word first, and a plain-text description of about 150-160 characters written on purpose.

## Bad — do not do this

`design-meta-title-and-description-bad`

```tsx
const TITLE = 'Home | Best Free AI Tools 2026 | Fast | Cheap | MyApp';
const SERP_TITLE_BUDGET = 60;

export function MetaTitleAndDescriptionBad() {
  const overBudget = Math.max(0, TITLE.length - SERP_TITLE_BUDGET);
  const shown =
    overBudget > 0 ? `${TITLE.slice(0, SERP_TITLE_BUDGET).trimEnd()}…` : TITLE;

  return (
    <div className="w-full space-y-4">
      {/* Mock search result */}
      <div className="rounded-lg border border-border bg-card p-3 space-y-1">
        <p className="mb-1 text-xs text-muted-foreground">Search result</p>
        <p className="text-xs text-muted-foreground">myapp.com › home</p>
        <p className="text-sm font-medium text-primary">{shown}</p>
        <p className="text-xs text-muted-foreground italic">
          Skip to content. Toggle menu. Cookie preferences. We use cookies to improve your…
        </p>
        <p className="text-xs text-error">
          No meta description — the snippet is scraped from whatever body copy came first.
        </p>
      </div>

      {/* Character ruler */}
      <div className="space-y-1">
        <div className="flex h-1.5 w-full overflow-hidden rounded-full bg-muted">
          <div className="h-full bg-primary" style={{ width: `${(SERP_TITLE_BUDGET / TITLE.length) * 100}%` }} />
          <div className="h-full bg-destructive" style={{ width: `${(overBudget / TITLE.length) * 100}%` }} />
        </div>
        <p className="text-xs text-error">
          {TITLE.length} characters — {overBudget} past the ~{SERP_TITLE_BUDGET}-char budget, truncated away
        </p>
      </div>

      <pre className="overflow-x-auto rounded-lg bg-muted p-3 text-xs text-foreground"><code>{`<title>${TITLE}</title>
<!-- no <meta name="description"> -->`}</code></pre>

      <p className="text-xs text-error">
        Keyword-stuffed and unreadable, and the part that identifies the product is the part that
        gets cut. With no description, the engine writes the snippet for you.
      </p>
    </div>
  );
}
```

## Good — do this

`design-meta-title-and-description-good`

```tsx
const TITLE = 'Pricing — MyApp';
const DESCRIPTION =
  'Compare MyApp plans: Free for solo projects, Pro for growing teams, Enterprise for scale. Transparent per-seat pricing, no setup fees, cancel any time.';
const SERP_TITLE_BUDGET = 60;
const SERP_DESC_BUDGET = 160;

export function MetaTitleAndDescriptionGood() {
  return (
    <div className="w-full space-y-4">
      {/* Mock search result */}
      <div className="rounded-lg border border-border bg-card p-3 space-y-1">
        <p className="mb-1 text-xs text-muted-foreground">Search result</p>
        <p className="text-xs text-muted-foreground">myapp.com › pricing</p>
        <p className="text-sm font-medium text-primary">{TITLE}</p>
        <p className="text-xs text-muted-foreground">{DESCRIPTION}</p>
      </div>

      {/* Character ruler */}
      <div className="space-y-2">
        <div className="space-y-1">
          <div className="h-1.5 w-full overflow-hidden rounded-full bg-muted">
            <div
              className="h-full bg-success"
              style={{ width: `${(TITLE.length / SERP_TITLE_BUDGET) * 100}%` }}
            />
          </div>
          <p className="text-xs text-muted-foreground">
            Title: {TITLE.length}/{SERP_TITLE_BUDGET} characters — nothing truncated
          </p>
        </div>
        <div className="space-y-1">
          <div className="h-1.5 w-full overflow-hidden rounded-full bg-muted">
            <div
              className="h-full bg-success"
              style={{ width: `${(DESCRIPTION.length / SERP_DESC_BUDGET) * 100}%` }}
            />
          </div>
          <p className="text-xs text-muted-foreground">
            Description: {DESCRIPTION.length}/{SERP_DESC_BUDGET} characters — plain text, written on purpose
          </p>
        </div>
      </div>

      <pre className="overflow-x-auto rounded-lg bg-muted p-3 text-xs text-foreground"><code>{`// one template, every page: \`\${page} — \${SITE_NAME}\`
<title>${TITLE}</title>
<meta name="description" content="${DESCRIPTION}" />`}</code></pre>

      <p className="text-xs text-success">
        One title template site-wide, so every page reads the same way and the distinctive word comes
        first. A written description means the snippet says what you chose, not what the crawler found.
      </p>
    </div>
  );
}
```

## References

- [fixing-metadata (skill source)](https://raw.githubusercontent.com/ibelick/ui-skills/main/skills/fixing-metadata/SKILL.md)
- [Google: Influencing your title links](https://developers.google.com/search/docs/appearance/title-link)
- [Google: Control your snippets](https://developers.google.com/search/docs/appearance/snippet)
