---
title: "useHead() · Unhead"
canonical_url: "https://unhead.unjs.io/docs/typescript/head/api/composables/use-head"
last_updated: "2026-07-21T08:17:57.619Z"
meta:
  description: "Manage document head tags with useHead(). Set titles, meta tags, scripts, and styles with typed input and reactive updates."
  "og:description": "Manage document head tags with useHead(). Set titles, meta tags, scripts, and styles with typed input and reactive updates."
  "og:title": "useHead() · Unhead"
---

Home

`
Unhead on GitHub

Switch to TypeScriptSwitch to VueSwitch to ReactSwitch to SvelteSwitch to Solid.jsSwitch to AngularSwitch to Nuxt

**Composables**

# **useHead()**

[Copy for LLMs](https://raw.githubusercontent.com/unjs/unhead/refs/heads/main/docs/head/7.api/composables/0.use-head.md)

```
import { useHead } from '@unhead/vue' // or your framework

useHead(unheadInstance, {
  title: 'Page Title',
  meta: [{ name: 'description', content: 'Page description' }],
})
```

`useHead()` registers typed head elements and returns an entry that you can update or remove.

```
import { useHead } from 'unhead'

const entry = useHead({
  title: 'My Page',
})
// update
entry.patch({ title: 'New Title' })
// remove
entry.dispose()
```

## How It Works

`**useHead()**` adds the input to the active head instance. At render time, Unhead resolves reactive values, [**~~deduplicates~~**](https://unhead.unjs.io/docs/head/guides/core-concepts/handling-duplicates), merges, and [**~~sorts~~**](https://unhead.unjs.io/docs/head/guides/core-concepts/positions) all active entries before producing DOM or SSR output.

You won't know the final state of the head until the rendering is complete.

## API Reference

```
function useHead(input?: UseHeadInput, options?: HeadEntryOptions): ActiveHeadEntry
```

### Parameters

| **Parameter** | **Type** | **Required** | **Description** |
| --- | --- | --- | --- |
| `**input**` | `**UseHeadInput**` | No | The head configuration object; defaults to an empty object |
| `**options**` | `**HeadEntryOptions**` | No | Configuration options for the head entry |

### Returns

```
interface ActiveHeadEntry {
  /**
   * Update the head entry with new values
   */
  patch: (input: UseHeadInput) => void
  /**
   * Remove the head entry
   */
  dispose: () => void
}
```

## Input Schema

The input object accepts the following properties:

```
interface ResolvableHead {
  // Document title
  title?: string | (() => string)

  // Title template (function or string with %s placeholder)
  titleTemplate?: string | null | ((title?: string) => string | null)

  // Template parameters for dynamic replacements
  templateParams?: { separator?: string } & Record<string, null | string | Record<string, string>>

  // HTML tag collections
  base?: Base
  link?: Link[]
  meta?: Meta[]
  style?: (Style | string)[]
  script?: (Script | string)[]
  noscript?: (Noscript | string)[]

  // Element attributes
  htmlAttrs?: HtmlAttributes<E['htmlAttrs']>
  bodyAttrs?: BodyAttributes<E['bodyAttrs']>
}
```

Most input values are deeply resolved, so functions can read current state when the head renders:

```
import { useHead } from 'unhead'

const title = useMyTitle()
useHead(unheadInstance, {
  // Read the current value each time the head resolves.
  title: () => 'Dynamic Title',
  meta: [
    () => ({
      name: 'description',
      content: () => \`Description for ${title.value}\`
    }),
  ]
})
```

## Options

The `**options**` parameter allows you to configure the behavior of the head entry:

```
export interface HeadEntryOptions {
  // Whether to process template parameters in the input
  // - Requires the TemplateParams plugin
  processTemplateParams?: boolean

  // Priority of tags for determining render order
  tagPriority?: number | 'critical' | 'high' | 'low' | \`before:${string}\` | \`after:${string}\`

  // Where to position tags in the document
  tagPosition?: 'head' | 'bodyClose' | 'bodyOpen'

  // Explicit dedupe key and duplicate handling strategy
  key?: string
  tagDuplicateStrategy?: 'replace' | 'merge'

  // Callback fired after DOM updates are applied (client-only, ignored during SSR)
  onRendered?: (ctx: { renders: DomRenderTagContext[] }) => void | Promise<void>

  // Custom head instance
  head?: Unhead
}
```

An option applies to every tag in the entry. This example gives two fallback tags low priority:

```
import { useHead } from 'unhead'

useHead(unheadInstance, {
  meta: [
    { name: 'description', content: 'fallback description' },
    { name: 'author', content: 'fallback author' }
  ]
}, {
  tagPriority: 'low'
})
```

## Synchronizing with DOM Updates

The `**onRendered**` option runs after Unhead applies DOM updates. Use it when another client-side tool must read the updated head state:

```
import { useHead } from 'unhead'

useHead(unheadInstance, {
  title: 'My Page',
}, {
  onRendered({ renders }) {
    // document.title is guaranteed to be up-to-date here
    analytics.track('Page View', { title: document.title })
  }
})
```

- The callback fires on **every** DOM render while the entry is active, not just the first.
- It is **ignored during SSR**; it only runs on the client.
- A returned promise does not delay DOM rendering. Start asynchronous work from the callback, but do not use it as a render barrier.
- The callback is automatically cleaned up when the entry is disposed (including on component unmount in frameworks).
- The `**renders**` array contains the render context for each tag that was processed.

It works with every composable that accepts `**HeadEntryOptions**`: `**useHead()**`, `**useSeoMeta()**`, and `**useHeadSafe()**`.

## Reactivity

### Automatic Reactivity

The `useHead()` composable automatically integrates with your framework's reactivity system:

```
import { useHead } from 'unhead'
import { computed, ref } from 'vue'

const title = ref('Dynamic Title')

useHead(unheadInstance, {
  title,
  meta: [
    { name: 'description', content: computed(() => \`Description for ${title.value}\`) }
  ]
})
```

### Manual Control

Use the returned entry when updates are not driven by framework reactivity:

```
import { useHead } from 'unhead'

// Create the head entry
const headControl = useHead({
  title: 'Initial Title'
})

// Later update specific fields
headControl.patch({
  title: 'Updated Title',
  meta: [
    { name: 'description', content: 'New description' }
  ]
})

// Remove the entry entirely when needed
headControl.dispose()
```

## Security Considerations

`useHead()` normalizes and serializes tags, but it is not an HTML, URL, JavaScript, or CSS sanitizer. Do not pass it untrusted or third-party input.

For XSS protection, either:

1. Sanitize your input before passing it to `useHead()`
2. Use the safer alternatives:
   - [**~~useSeoMeta()~~**](https://unhead.unjs.io/docs/head/api/composables/use-seo-meta) for SEO metadata
   - [**~~useHeadSafe()~~**](https://unhead.unjs.io/docs/head/api/composables/use-head-safe) for general head management

Sanitization must match the destination context: HTML, attributes, URLs, JavaScript, and CSS require different controls. See the [**~~OWASP XSS Prevention Cheat Sheet~~**](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).

## TypeScript

Import types directly from your framework's package:

```
import type { ActiveHeadEntry, Head, HeadEntryOptions, UseHeadInput } from '@unhead/vue'

// Type your head input
const headConfig: Head = {
  title: 'My Page',
  meta: [{ name: 'description', content: 'Page description' }]
}

// Type your entry options
const options: HeadEntryOptions = {
  tagPriority: 'high'
}

const entry: ActiveHeadEntry<UseHeadInput> = useHead(headConfig, options)
```

The tag types accept `**data-***` attributes. Use `**defineLink()**` and `**defineScript()**` for non-standard `**rel**` and `**type**` values, as shown below.

### Type Narrowing

`**Link**` and `**Script**` are discriminated unions keyed on `**rel**` and `**type**`. Known values enforce their required properties. For a non-standard value, use `**defineLink**` or `**defineScript**`; the helpers retain strict types for known values and fall through to `**GenericLink**` or `**GenericScript**` otherwise:

```
import { defineLink, useHead } from 'unhead'

useHead(unheadInstance, {
  link: [
    { rel: 'canonical', href: 'https://example.com' }, // known rel, works directly
    { rel: 'me', href: 'https://mastodon.social/@me' }, // known rel, works directly
    defineLink({ rel: 'openid2.provider', href: 'https://example.com/openid' }), // non-standard rel
  ]
})
```

When building link or script objects outside of `useHead()`, use `**as const**` on literal values to preserve type narrowing:

```
const link = { rel: 'preload' as const, as: 'font' as const, href: '/font.woff2', crossorigin: 'anonymous' as const }
useHead(unheadInstance, { link: [link] })
```

`useSeoMeta()` is unaffected by type narrowing and remains the simplest path for SEO meta tags.

## Common Mistakes

### Using reactive values incorrectly

```
// ❌ Wrong - loses reactivity
const title = ref('My Title')
useHead(unheadInstance, { title: title.value })

// ✅ Correct - pass the ref directly
useHead(unheadInstance, { title })
```

### Calling useHead in async code

```
// ❌ Wrong - may execute outside component context
async function loadData() {
  const data = await fetchData()
  useHead({ title: data.title }) // Context may be lost
}

// ✅ Correct - set up head first, update reactively
const data = ref(null)
useHead(unheadInstance, { title: () => data.value?.title ?? 'Loading...' })
async function loadData() {
  data.value = await fetchData()
}
```

### Forgetting to dispose manual entries

```
// ❌ Memory leak if called multiple times
function showModal() {
  useHead({ title: 'Modal Open' })
}

// ✅ Store and dispose when done
let modalHead: ReturnType<typeof useHead> | null = null
function showModal() {
  modalHead = useHead({ title: 'Modal Open' })
}
function hideModal() {
  modalHead?.dispose()
  modalHead = null
}
```

## Choosing the Right Composable

| **Composable** | **Use When** |
| --- | --- |
| `**useHead()**` | General head management, including scripts and links |
| `**useSeoMeta()**` | SEO meta tags with type-safe keys |
| `**useHeadSafe()**` | Working with untrusted/user-provided input |
| `**useScript()**` | Loading third-party scripts with lifecycle control |

Use `**useSeoMeta()**` for SEO fields and `**useHead()**` when you need arbitrary tags, attributes, or entry options.

## Common Questions

### How do I update the title dynamically?

Use a reactive value or the `**patch()**` method:

```
const entry = useHead({ title: 'Initial' })
entry.patch({ title: 'Updated Title' })
```

### How do I remove head tags?

Call `**dispose()**` on the returned entry:

```
const entry = useHead({ title: 'Temporary' })
entry.dispose() // removes all tags from this entry
```

## Advanced Examples

### Title Template

```
import { useHead } from 'unhead'

useHead(unheadInstance, {
  titleTemplate: title => \`${title} - My Site\`,
  title: 'Home Page'
})
// Results in: "Home Page - My Site"
```

For more details on title templates, see the [**~~Titles guide~~**](https://unhead.unjs.io/docs/head/guides/core-concepts/titles).

### Combining Multiple Head Entries

```
import { useHead } from 'unhead'

// Global site defaults
useHead(unheadInstance, {
  titleTemplate: '%s | My Website',
  meta: [
    { property: 'og:site_name', content: 'My Website' }
  ]
})

// Page-specific entries (will be merged with globals)
useHead(unheadInstance, {
  title: 'Product Page',
  meta: [
    { name: 'description', content: 'A compact mechanical keyboard with hot-swappable switches' }
  ]
})
```

### Async Data Loading

```
import { useHead } from 'unhead'
import { computed, ref } from 'vue'

const data = ref(null)
const loading = ref(true)

useHead(unheadInstance, {
  title: computed(() => data.value
    ? \`${data.value.name} - Product\`
    : loading.value
      ? 'Loading...'
      : 'Product Not Found')
})

async function fetchProduct(id) {
  loading.value = true
  data.value = await api.getProduct(id)
  loading.value = false
}
```

### Priority-Based Tag Ordering

```
import { useHead } from 'unhead'

// Critical meta tags (early in <head>)
useHead(unheadInstance, {
  meta: [
    { charset: 'utf-8' },
    { name: 'viewport', content: 'width=device-width, initial-scale=1' }
  ]
}, { tagPriority: 'critical' })

// Default priority tags (middle of <head>)
useHead(unheadInstance, {
  meta: [
    { name: 'description', content: 'My website description' }
  ]
})

// Low priority tags (end of <head>)
useHead(unheadInstance, {
  meta: [
    { name: 'author', content: 'Jane Doe' }
  ]
}, { tagPriority: 'low' })
```

[~~Edit this page~~](https://github.com/unjs/unhead/edit/main/docs/head/7.api/composables/0.use-head.md)

[~~Markdown For LLMs~~](https://raw.githubusercontent.com/unjs/unhead/refs/heads/main/docs/head/7.api/composables/0.use-head.md)

**Did this page help you? **

[**Overview** API reference for useHead(), useSeoMeta(), and useScript(), plus DOM and SSR rendering hooks with TypeScript signatures.](https://unhead.unjs.io/docs/head/api/get-started/overview) [**useHeadSafe()** Filter untrusted head input through a restrictive tag and attribute allowlist with useHeadSafe().](https://unhead.unjs.io/docs/head/api/composables/use-head-safe)

**On this page **

- [How It Works](#how-it-works)
- [API Reference](#api-reference)
- [Input Schema](#input-schema)
- [Options](#options)
- [Synchronizing with DOM Updates](#synchronizing-with-dom-updates)
- [Reactivity](#reactivity)
- [Security Considerations](#security-considerations)
- [TypeScript](#typescript)
- [Common Mistakes](#common-mistakes)
- [Choosing the Right Composable](#choosing-the-right-composable)
- [Common Questions](#common-questions)
- [Advanced Examples](#advanced-examples)