# Keep Nouns Consistent

**MUST** · **ID:** `content-consistent-nouns` · **Category:** content
**Source:** [Vercel](https://github.com/vercel-labs/agent-skills/blob/main/skills/web-design-guidelines/SKILL.md)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/content-consistent-nouns

> MUST: Name each object once and reuse that exact noun in every label, heading, toast, and error — project/app/site/deployment for one concept reads as four things. Introduce as few unique terms as possible and keep a glossary.

Name each object once and reuse that exact noun everywhere it appears in the product

When one screen calls the same object a project, an app, a site, and a deployment, the reader has to decide whether those are four things or one — and the safe assumption is four. Synonyms feel like good writing and behave like a bug: they break search, they break the mental model, and they make docs, support replies, and API names drift apart. Pick one noun per concept, write it in a glossary, and use it in every label, heading, toast, and error until the concept itself changes.

## Bad — do not do this

`content-consistent-nouns-bad`

```tsx
// One object. Four names for it, all on the same screen.
const SYNONYMS = ['project', 'app', 'site', 'deployment', 'Project', 'App', 'Site', 'Deployment'];
const SYNONYM_PATTERN = new RegExp(`\\b(${SYNONYMS.join('|')})\\b`, 'g');

const LINES = [
  { label: 'Heading', text: 'Project settings' },
  { label: 'Subhead', text: 'Configure how your app builds and runs.' },
  { label: 'Field', text: 'Site name' },
  { label: 'Help text', text: 'This name appears in the URL of every deployment.' },
  { label: 'Button', text: 'Delete this app' },
  { label: 'Toast', text: 'Your site was removed.' },
];

const UNIQUE_TERMS = new Set(
  LINES.flatMap((line) => line.text.match(SYNONYM_PATTERN) ?? []).map((term) => term.toLowerCase()),
);

function Highlighted({ text }: { text: string }) {
  const parts = text.split(SYNONYM_PATTERN);
  return (
    <span className="text-sm text-foreground">
      {parts.map((part, i) =>
        // split() with a capture group puts the matches at odd indices
        i % 2 === 1 ? (
          <mark key={i} className="rounded bg-error/15 px-1 font-semibold text-error">
            {part}
          </mark>
        ) : (
          <span key={i}>{part}</span>
        ),
      )}
    </span>
  );
}

export function ConsistentNounsBad() {
  return (
    <div className="w-full max-w-md space-y-3">
      <div className="divide-y divide-border rounded-lg border border-border bg-card">
        {LINES.map((line) => (
          <div key={line.label} className="flex items-baseline gap-3 p-3">
            <span className="w-20 shrink-0 text-xs uppercase tracking-wide text-muted-foreground">
              {line.label}
            </span>
            <Highlighted text={line.text} />
          </div>
        ))}
      </div>

      <div className="flex flex-wrap items-center gap-2 text-xs">
        <span className="rounded border border-border bg-muted px-2 py-1 font-mono text-error">
          unique terms: {UNIQUE_TERMS.size}
        </span>
        <span className="text-muted-foreground">
          {[...UNIQUE_TERMS].join(' · ')} — all the same object
        </span>
      </div>

      <p className="text-xs text-error">
        Six strings, four nouns, one thing. The reader has to guess whether deleting the “app” also
        deletes the “site”, and search for “project” never finds the button.
      </p>
    </div>
  );
}
```

## Good — do this

`content-consistent-nouns-good`

```tsx
const TERM_PATTERN = /\b(project|Project)\b/g;

const LINES = [
  { label: 'Heading', text: 'Project settings' },
  { label: 'Subhead', text: 'Configure how your project builds and runs.' },
  { label: 'Field', text: 'Project name' },
  { label: 'Help text', text: 'This name appears in the URL of every project preview.' },
  { label: 'Button', text: 'Delete this project' },
  { label: 'Toast', text: 'Your project was removed.' },
];

const UNIQUE_TERMS = new Set(
  LINES.flatMap((line) => line.text.match(TERM_PATTERN) ?? []).map((term) => term.toLowerCase()),
);

function Highlighted({ text }: { text: string }) {
  const parts = text.split(TERM_PATTERN);
  return (
    <span className="text-sm text-foreground">
      {parts.map((part, i) =>
        i % 2 === 1 ? (
          <mark key={i} className="rounded bg-success/15 px-1 font-semibold text-success">
            {part}
          </mark>
        ) : (
          <span key={i}>{part}</span>
        ),
      )}
    </span>
  );
}

export function ConsistentNounsGood() {
  return (
    <div className="w-full max-w-md space-y-3">
      <div className="divide-y divide-border rounded-lg border border-border bg-card">
        {LINES.map((line) => (
          <div key={line.label} className="flex items-baseline gap-3 p-3">
            <span className="w-20 shrink-0 text-xs uppercase tracking-wide text-muted-foreground">
              {line.label}
            </span>
            <Highlighted text={line.text} />
          </div>
        ))}
      </div>

      <div className="flex flex-wrap items-center gap-2 text-xs">
        <span className="rounded border border-border bg-muted px-2 py-1 font-mono text-success">
          unique terms: 4 → {UNIQUE_TERMS.size}
        </span>
        <span className="text-muted-foreground">
          {[...UNIQUE_TERMS].join(' · ')} — everywhere, no exceptions
        </span>
      </div>

      <p className="text-xs text-success">
        One noun per concept. The button, the toast, the docs, and the API all say “project”, so
        search works and nobody wonders if these are different objects.
      </p>
    </div>
  );
}
```

## References

- [Vercel Web Interface Guidelines](https://github.com/vercel-labs/web-interface-guidelines)
- [NN/g: Consistency and Standards](https://www.nngroup.com/articles/consistency-and-standards/)
- [Google Style: Word List](https://developers.google.com/style/word-list)
