Skip to content

useFavicon()

Category
Export Size
1.2 kB
Gzipped
524 B
SSR
Not support

Last updated: 24/04/2026

Overview

useFavicon keeps the current favicon path in React state and, on every change, finds or creates a <link rel="…"> in the document head, sets its href to baseUrl + filename, and sets type from the file extension so the browser swaps the tab icon without a full reload. You can point at static assets under a CDN path (baseUrl), match an existing link via rel (for example icon vs shortcut icon), or pass a custom doc when updating a non-default document (tests, embedded HTML). The hook returns a tuple [favicon, setFavicon] like useState, so you can seed an initial icon from newIcon and drive updates from UI or side effects.

What it accepts

  • Optional newIcon - Initial filename or path fragment (joined with baseUrl). Default ''.
  • Optional baseUrl - Prefix for the final href (for example /icons/ or a CDN origin). Default ''.
  • Optional rel - rel selector / attribute for the link node (querySelector('link[rel*="…"]')). Default 'icon'.
  • Optional doc - Target Document (default document).

What it returns

  • A tuple [favicon, setFavicon]: current icon string and a state setter that triggers a head <link> update on the next commit.

Usage

Example: default vs “busy” favicon under a shared asset prefix, with an explicit rel and document (all options visible).

tsx
import useFavicon from '@dedalik/use-react/useFavicon'

function Example() {
  const [favicon, setFavicon] = useFavicon({
    newIcon: 'favicon.ico',
    baseUrl: '/brand/icons/',
    rel: 'icon',
    doc: document,
  })

  return (
    <div>
      <h3>Tab icon</h3>
      <p>
        Current file: <strong>{favicon || '-'}</strong>
      </p>
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
        <button type='button' onClick={() => setFavicon('favicon.ico')}>
          Default
        </button>
        <button type='button' onClick={() => setFavicon('favicon-busy.png')}>
          Busy
        </button>
      </div>
      <p style={{ marginTop: 12, opacity: 0.75 }}>
        The tab icon updates without a full reload; in devtools → Elements → head, the matching link href becomes{' '}
        <code>/brand/icons/</code> plus the filename.
      </p>
    </div>
  )
}

export default function Demo() {
  return <Example />
}

API Reference

useFavicon

Signature: useFavicon(options?: UseFaviconOptions): UseFaviconReturnType

Parameters

Optional object UseFaviconOptions:

  • newIcon (string, optional) - Initial icon filename or suffix; combined with baseUrl for href. Default ''.
  • baseUrl (string, optional) - Prefix prepended to newIcon. Default ''.
  • rel (string, optional) - Used to find/create the <link> (link[rel*="…"]). Default 'icon'.
  • doc (Document, optional) - Document whose <head> is updated. Default document.

Returns

Tuple [favicon, setFavicon] (UseFaviconReturnType):

  • favicon - Current icon string in state.
  • setFavicon - React state setter; updates state and re-runs the effect that applies the icon.

Copy-paste hook

TypeScript

tsx
import React, { useState, useEffect, useCallback } from 'react'

export interface UseFaviconOptions {
  newIcon?: string
  baseUrl?: string
  rel?: string
  doc?: Document
}

export type UseFaviconReturnType = [string, React.Dispatch<React.SetStateAction<string>>]

const useFavicon = ({
  newIcon = '',
  baseUrl = '',
  rel = 'icon',
  doc = document,
}: UseFaviconOptions = {}): UseFaviconReturnType => {
  const [favicon, setFavicon] = useState<string>(newIcon)

  const applyIcon = useCallback(
    (icon: string) => {
      let linkElement = doc.head.querySelector(`link[rel*="${rel}"]`) as HTMLLinkElement

      if (!linkElement) {
        linkElement = doc.createElement('link')
        linkElement.rel = rel
        doc.head.appendChild(linkElement)
      }

      const iconUrl = `${baseUrl}${icon}`
      if (linkElement.href !== iconUrl) {
        linkElement.href = iconUrl
        linkElement.type = `image/${icon.split('.').pop()}`
      }
    },
    [baseUrl, rel, doc],
  )

  useEffect(() => {
    if (typeof favicon === 'string') {
      applyIcon(favicon)
    }
  }, [favicon, applyIcon])

  return [favicon, setFavicon]
}

export default useFavicon

export type UseFaviconType = ReturnType<typeof useFavicon>

JavaScript

js
import React, { useState, useEffect, useCallback } from 'react'

const useFavicon = ({ newIcon = '', baseUrl = '', rel = 'icon', doc = document } = {}) => {
  const [favicon, setFavicon] = useState(newIcon)

  const applyIcon = useCallback(
    (icon) => {
      let linkElement = doc.head.querySelector(`link[rel*="${rel}"]`)

      if (!linkElement) {
        linkElement = doc.createElement('link')
        linkElement.rel = rel
        doc.head.appendChild(linkElement)
      }

      const iconUrl = `${baseUrl}${icon}`
      if (linkElement.href !== iconUrl) {
        linkElement.href = iconUrl
        linkElement.type = `image/${icon.split('.').pop()}`
      }
    },
    [baseUrl, rel, doc],
  )

  useEffect(() => {
    if (typeof favicon === 'string') {
      applyIcon(favicon)
    }
  }, [favicon, applyIcon])

  return [favicon, setFavicon]
}

export default useFavicon

Released under the MIT License.