How to Fix Symbols Across Fonts (®™©) with CSS and JS

Last updated on August 11th, 2025 at 11:56 am

Have you ever noticed that special characters like ®, ™, and © appear inconsistently across different fonts on your website? Sometimes they’re oversized, misaligned, or just don’t blend well with the surrounding text. This inconsistency can disrupt the visual harmony of the design, and make for unhappy customers. So here’s how we fix symbols style and consistency in wordpress.

The Problem

Special characters such as the registered trademark (®), trademark (™), and copyright (©) symbols can render differently depending on the font used. Some fonts may display these symbols larger or smaller than the surrounding text, leading to a jarring visual experience.

The Solution

To ensure consistent styling of these special characters across your website, in Fuel LAB® we normally use a combination of CSS and JavaScript. The idea is to wrap these symbols in a <span> with a specific class and then apply uniform styling to that class.

You can also just download the wp_code snippet here, if you don’t need the the TreeWalker edit 🙂

Step 1: Define the CSS for your special characters

First, add the following CSS to your stylesheet to style the special characters uniformly:

CSS
.reg-symbol {
  font-size: 0.55em;
  position: relative;
  top: -0.4em;
  line-height: 0;
  vertical-align: baseline;
  font-weight: normal;
  display: inline-block;
}

This CSS sets a consistent size and vertical alignment for the symbols, ensuring they blend seamlessly with the surrounding text.

Step 2: Implement the JavaScript

Next, use the following JavaScript to automatically wrap the special characters in the defined <span>:

JavaScript
<script>
document.addEventListener("DOMContentLoaded", function () {
  document.querySelectorAll("p, h1, h2, h3, h4, h5, h6, span, li, a").forEach(el => {
    el.childNodes.forEach(node => {
      if (node.nodeType === Node.TEXT_NODE && node.textContent.match(/[®™©]/)) {
        const replacedHTML = node.textContent
          .replace(/®/g, '<span class="reg-symbol">®</span>')
          .replace(//g, '<span class="reg-symbol">™</span>')
          .replace(/©/g, '<span class="reg-symbol">©</span>');
        const spanWrapper = document.createElement('span');
        spanWrapper.innerHTML = replacedHTML;
        el.replaceChild(spanWrapper, node);
      }
    });
  });
});
</script>

This script waits for the DOM to load, then searches through specified elements for text nodes containing the special characters. When found, it wraps them in a <span> with the reg-symbol class.

What if it doesn’t work?

There are a number of situations where your ® mark is included in an inner structure of the block, as an example bold tags. This applies to <b>, <strong>, <i>, <span>, ecc

In this case, you can try a different approach using Tree Walker

JavaScript
document.addEventListener("DOMContentLoaded", function () {
  const elements = document.querySelectorAll("p, h1, h2, h3, h4, h5, h6, span, li, a");

  elements.forEach(el => {
    const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
    let node;

    while ((node = walker.nextNode())) {
      if (node.textContent.match(/[®™©]/)) {
        const replacedHTML = node.textContent
          .replace(/®/g, '<span class="reg-symbol">®</span>')
          .replace(//g, '<span class="reg-symbol">™</span>')
          .replace(/©/g, '<span class="reg-symbol">©</span>');

        const spanWrapper = document.createElement('span');
        spanWrapper.innerHTML = replacedHTML;

        node.parentNode.replaceChild(spanWrapper, node);
      }
    }
  });
});

What if the symbols are still rendering the same

In this case, it could be your theme builder or page builder is injecting after the DOM is ready the dynamic classes on spans, p, headings and such. In that case, you might want to try a different approach (tree walker), such as the following example I’ve made for Divi:

Mojo
<script>
(function () {
  // Regex to detect any of the target symbols
  const SYMBOL_RE = /[®™©]/;
  const SPLIT_RE = /[®™©]/g;
  
  // Root selector where processing will start
  // Change this if you want to limit scope, e.g., '.et_builder_inner_content'
  const rootSelector = 'body';

  /**
   * Processes a single text node and replaces ®™© with <span class="reg-symbol">...</span>
   * while keeping the original parent element and its attributes intact.
   */
  function processTextNode(node) {
    if (!node || !node.parentNode) return;

    const parent = node.parentNode;
    const tag = parent.nodeName;

    // Skip unwanted parent tags and text already inside .reg-symbol
    if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'TEXTAREA' || parent.closest('.reg-symbol')) return;

    const txt = node.nodeValue;
    if (!SYMBOL_RE.test(txt)) return; // Skip if no symbol found

    // Create a temporary fragment to hold the new node structure
    const frag = document.createDocumentFragment();
    let last = 0;

    // Go through each match and replace it with a span
    txt.replace(SPLIT_RE, (match, idx) => {
      // Append text before the symbol
      if (idx > last) frag.appendChild(document.createTextNode(txt.slice(last, idx)));
      
      // Create the span for the symbol
      const span = document.createElement('span');
      span.className = 'reg-symbol';
      span.textContent = match; // Safe text insertion (avoids HTML injection)
      frag.appendChild(span);

      last = idx + 1;
      return match;
    });

    // Append any remaining text after the last symbol
    if (last < txt.length) frag.appendChild(document.createTextNode(txt.slice(last)));

    // Replace the original text node with our fragment
    parent.replaceChild(frag, node);
  }

  /**
   * Processes all eligible text nodes within a given root element.
   * Uses TreeWalker to find text nodes and avoids script/style/textarea.
   */
  function processContainer(root) {
    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
      acceptNode(n) {
        if (!n.parentNode) return NodeFilter.FILTER_REJECT;
        const p = n.parentNode;
        const tag = p.nodeName;
        if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'TEXTAREA') return NodeFilter.FILTER_REJECT;
        return SYMBOL_RE.test(n.nodeValue) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
      }
    });

    const toProcess = [];
    let node;
    while ((node = walker.nextNode())) {
      toProcess.push(node);
    }

    // Process after collection to avoid breaking the walker
    toProcess.forEach(processTextNode);
  }

  /**
   * Initializes the script:
   * - Processes the initial content
   * - Sets up a MutationObserver to handle dynamically inserted content
   */
  function init() {
    const root = document.querySelector(rootSelector);
    if (!root) return;

    // Process existing content
    processContainer(root);

    // Observe for future changes (Divi animations, AJAX, lazy load, etc.)
    const obs = new MutationObserver(mutations => {
      for (const m of mutations) {
        if (m.type === 'characterData') {
          processTextNode(m.target);
        } else if (m.type === 'childList') {
          m.addedNodes.forEach(n => {
            if (n.nodeType === 3) {
              processTextNode(n); // Direct text node
            } else if (n.nodeType === 1) {
              processContainer(n); // Element: process all its text nodes
            }
          });
        }
      }
    });

    obs.observe(root, {
      childList: true,       // Watch for new/removed elements
      characterData: true,   // Watch for text changes
      subtree: true          // Include all descendants
    });
  }

  // Run after page load to ensure we beat late scripts that change the DOM
  if (document.readyState === 'complete') {
    setTimeout(init, 0);
  } else {
    window.addEventListener('load', () => setTimeout(init, 0));
  }
})();
</script>

Conclusion

If you’re working with wordpress, this is a pretty stable way to ensure that special characters like ®, ™, and © are styled consistently across your website, regardless of the fonts used. Of course feel free to edit the css part of the snippet accordingly to your design choices.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.