---
title: "Page Titles with Unhead"
description: "Manage page titles with useHead, title templates, reactive values, social metadata, and template params."
canonical_url: "https://unhead.unjs.io/docs/head/guides/core-concepts/titles"
last_updated: "2026-08-11T00:46:00.857Z"
---

Use `useHead({ title: 'Your Title' })` for a title that works in both server and client rendering. Add `titleTemplate: '%s | Site Name'` when pages share the same suffix.

```html
<head>
  <title>Mastering Titles · My App</title>
</head>
```

## The title element

The `<title>` tag labels the document in browser tabs and is one of several sources Google may use to generate a search-result title link.

Setting `document.title` directly works only in a browser:

```ts
// Browser-only: document is unavailable during SSR
document.title = 'Home'
```

<warning>

`document.title` is unavailable during server-side rendering and bypasses Unhead's server output.

</warning>

Google documents how page titles and other signals can influence [title links in search results](https://developers.google.com/search/docs/appearance/title-link).

## Dynamic Page Titles with `useHead()`

Use [`useHead()`](/docs/head/api/composables/use-head) to register a title with the active head instance:

<code-group>

```ts [Framework Agnostic]
import { useHead } from '@unhead/dynamic-import'

useHead({
  title: 'Home'
})
```

```html [output.html]
<head>
  <title>Home</title>
</head>
```

</code-group>

The entry is included in server output and kept in sync on the client. The same entry can include other head tags:

```ts
import { useHead } from '@unhead/dynamic-import'

useHead({
  title: 'Home',
  meta: [
    { name: 'description', content: 'Welcome to MyApp' }
  ]
})
```

<tip>

Unhead automatically deduplicates title tags set by multiple entries. With equal priorities, the most recently registered title takes precedence.

</tip>

Reactivity follows the conventions of each framework adapter; the `useHead()` input shape stays the same.

## Shared site names

Set a shared site name and separator with a title template:

```html
<head>
  <title>Home | MySite</title>
</head>
```

<code-group>

```ts [Framework Agnostic]
import { useHead } from '@unhead/dynamic-import'

useHead({
  title: 'Home',
  titleTemplate: '%s | MySite'
})
```

```html [output.html]
<head>
  <title>Home | MySite</title>
</head>
```

</code-group>

The `%s` placeholder in a string `titleTemplate` is replaced with the page title. Additional [template params](/docs/head/guides/plugins/template-params) require `TemplateParamsPlugin`.

### Template Params

Template params are an opt-in feature for making tags more dynamic. Register `TemplateParamsPlugin` when creating the head instance, then provide `templateParams` with your entry. The plugin supplies `%s`, `%separator`, and your custom parameters:

<code-block>

```ts [Input]
import { useHead } from '@unhead/dynamic-import'

useHead({
  title: 'Home',
  titleTemplate: '%s %separator %siteName',
  templateParams: {
    separator: '·',
    siteName: 'My Site'
  }
})
```

```html [Output]
<title>Home · My Site</title>
```

</code-block>

<note>

Template params are processed automatically in titles, meta `content`, link `href`, and the `lang` HTML attribute. Other inline content requires `processTemplateParams: true` on the tag.

</note>

See [Template Params](/docs/head/guides/plugins/template-params) for the complete placeholder rules.

### Resetting the Title Template

Pass `null` to `titleTemplate` to disable an inherited template for one page.

<code-group>

```vue [input.vue]
<script lang="ts" setup>
import { useHead } from '@unhead/dynamic-import'

useHead({
  title: 'Home',
  titleTemplate: null
})
</script>
```

```html [output.html]
<head>
  <title>Home</title>
</head>
```

</code-group>

### Social Share Titles

Social platforms use different meta tags for sharing titles.

<figure-image alt="Nuxt X Share" lazy="true" src="/nuxt-x-share.png">



</figure-image>

The example preview uses X-specific title metadata alongside the standard [`og:title`](https://ogp.me/) field. Use
[`useSeoMeta()`](/docs/head/api/composables/use-seo-meta) to set these fields by name:

<code-group>

```ts [Framework Agnostic]
import { useSeoMeta } from '@unhead/dynamic-import'

useSeoMeta({
  titleTemplate: '%s | Health Tips',
  title: 'Why you should eat more broccoli',
  // ogTitle is not affected by titleTemplate; use template params here if needed
  ogTitle: 'Hey! Health Tips - 10 reasons to eat more broccoli.',
  // Legacy X-specific override; twitterTitle is deprecated in favor of ogTitle
  twitterTitle: 'Hey X! Health Tips - 10 reasons to eat more broccoli.',
})
```

```html [output.html]
<head>
  <title>Why you should eat more broccoli | Health Tips</title>
  <meta property="og:title" content="Hey! Health Tips - 10 reasons to eat more broccoli." />
  <meta name="twitter:title" content="Hey X! Health Tips - 10 reasons to eat more broccoli." />
</head>
```

</code-group>

The `useSeoMeta` input shape is the same across Vue, React, Svelte, Solid, and Angular.

## Common Use Cases

### Reactive Titles

Pass a getter to update a title when component state changes:

<framework-code>
<template v-slot:vue="">

```ts
import { useHead } from '@unhead/dynamic-import'
import { ref } from 'vue'

const productName = ref('Widget X')
const isLoading = ref(true)

useHead({
  title: () => isLoading.value
    ? 'Loading...'
    : `Product: ${productName.value}`
})
```

</template>

<template v-slot:react="">

```tsx
import { useHead } from '@unhead/dynamic-import'
import { useState } from 'react'

function ProductPage() {
  const [productName, setProductName] = useState('Widget X')
  const [isLoading, setIsLoading] = useState(true)

  useHead({
    title: () => isLoading
      ? 'Loading...'
      : `Product: ${productName}`
  })

  return <div>Product Page</div>
}
```

</template>

<template v-slot:solid="">

```tsx
import { useHead } from '@unhead/dynamic-import'
import { createSignal } from 'solid-js'

function ProductPage() {
  const [productName, setProductName] = createSignal('Widget X')
  const [isLoading, setIsLoading] = createSignal(true)

  useHead({
    title: () => isLoading()
      ? 'Loading...'
      : `Product: ${productName()}`
  })

  return <div>Product Page</div>
}
```

</template>
</framework-code>

### Hierarchical Titles

For nested pages like documentation, show hierarchy:

```ts
import { useHead } from '@unhead/dynamic-import'

// Works in any framework
useHead({
  title: 'Installation',
  titleTemplate: '%s | Documentation | MyApp'
})
```

### Language-Specific Titles

For multilingual sites:

<code-group>

```ts [Framework Agnostic]
import { useHead } from '@unhead/dynamic-import'

// In a real app, you'd get this from your i18n library
const locale = 'en'

const titles = {
  en: 'Welcome',
  fr: 'Bienvenue',
  es: 'Bienvenido'
}

useHead({
  title: titles[locale] || titles.en,
  htmlAttrs: {
    lang: locale
  }
})
```

```tsx [React Example]
import { useHead } from '@unhead/dynamic-import'
import { useTranslation } from 'react-i18next' // Example i18n library

function HomePage() {
  const { t, i18n } = useTranslation()

  useHead({
    title: t('home.title'),
    htmlAttrs: {
      lang: i18n.language
    }
  })

  return <div>Home Page</div>
}
```

</code-group>

## Writing useful titles

Google recommends [descriptive, concise, page-specific title text](https://developers.google.com/search/docs/appearance/title-link). Search results truncate titles as needed for the device, so there is no fixed character limit.

<tip>

- Keep titles concise and descriptive rather than targeting a fixed character count
- Make each page title unique across your site
- Keep shared branding brief so the page-specific text remains distinct
- Ensure titles accurately describe the page content

</tip>
