HTML Entities: Decode Hex vs Decimal Escapes

Why Do HTML Entities Come in Two Number Formats Anyway?

So you're building a web-based character escaper—maybe a sanitizer, maybe a code formatter, maybe an encoder for your team's CMS—and suddenly you hit a fork in the road that nobody warned you about. Do you escape the copyright symbol as © or ©? Both render the exact same © on the page. Both pass validation. Both are perfectly legal HTML. So why does this choice even exist, and more importantly, which one should your tool be spitting out by default?

That's the question I'm going to answer for you end-to-end. By the time you finish reading, you'll have a clear, defensible default for your escaping logic—and you'll understand the few edge cases where you might want to switch formats. I'm writing this specifically for developers building tools that programmatically escape characters for HTML output, not for someone hand-coding a static page. Your tool needs to be consistent, predictable, and defensible when another dev reviews the PR.

What's the Actual Difference Between Hex and Decimal HTML Entities?

Let's get the mechanics straight first. An HTML numeric character reference points to a Unicode code point using this syntax:

&#N; — where N is a decimal number.
&#xN; — where N is a hexadecimal number (the x or X flags the base).

Same target, two notations. Take the right single quotation mark—better known as the curly apostrophe. Its Unicode code point is U+2019. In decimal, that code point is 8217. So you can write either ’ or ’, and the browser resolves both to the same character.

The Math Behind the Two

Here's the conversion so it's not a black box. The hex value 2019 breaks down as:

(2 × 4096) + (0 × 256) + (1 × 16) + (9 × 1) = 8192 + 0 + 16 + 9 = 8217

That's it. That's the entire difference at the mechanical level. One uses base-10, the other uses base-16 with an x prefix. The browser's HTML parser converts both to the same internal integer and fetches the same glyph from the same Unicode table.

Which Entity Format Should Your Dev Tool Output by Default?

Here's where I stop being neutral and give you a real recommendation. If you're building an escaping utility, default to decimal entities (&#NNNN;). Here's why this is the right call for a tool that other developers will rely on.

Decimal is universally unambiguous. Every developer, every linter, every regex pattern in the wild recognizes © as a numeric character reference. Hex entities, especially with a lowercase x, occasionally trip up poorly-written parsers or legacy systems that weren't built with the &#x prefix in mind. You don't want your tool's output to be the thing that exposes a bug in someone else's downstream pipeline.

When Hex Is the Better Choice

That said, there's one scenario where I'd flip the default: if your tool is specifically outputting content for XML, SVG, or XHTML contexts where developers are used to seeing hex notation in the source. In those ecosystems, hex is the cultural norm. ’ looks familiar to someone who works with SVG path data or XML attribute values. ’ looks slightly foreign.

Also, if your tool's output is meant to be read and edited by humans—like a generated HTML template that a designer will tweak—hex can be more readable because it preserves the Unicode code point's identity. A developer who sees ’ can mentally map it to U+2019 and look it up. ’ requires a conversion step before it's searchable in a Unicode chart.

How Do You Convert Between Hex and Decimal Entities in Code?

Let's walk through the actual implementation. Say your tool receives a character, looks up its code point, and needs to emit an entity. Here's the workflow in JavaScript, since that's what most web-based dev tools run on.

Step 1: Get the Code Point

Use codePointAt(), not charCodeAt(). The difference matters for characters outside the Basic Multilingual Plane—anything above U+FFFF, like emoji. charCodeAt() returns UTF-16 surrogate halves for those characters, which will produce broken entities. codePointAt() returns the full Unicode code point.

const cp = '©'.codePointAt(0); // returns 169

Step 2: Emit the Entity

For decimal output, just stringify the code point:

const decimalEntity = `&#${cp};`; // "©"

For hex output, convert to base-16 with toString(16):

const hexEntity = `&#x${cp.toString(16).toUpperCase()};`; // "©"

Notice I'm uppercasing the hex digits. This is a minor stylistic choice, but it matters for consistency. © and © are both valid, but mixing cases across your tool's output looks sloppy. Pick one—uppercase is the convention in most Unicode documentation—and stick with it.

Step 3: Handle the Full Escaping Pipeline

Your tool shouldn't blindly escape every character as a numeric entity. That would turn readable HTML into an unreadable mess of o for every o. The standard approach:

  1. Escape the five HTML-special characters (&, <, >, ", ') using their named entities (&amp;, &lt;, etc.).
  2. For everything else, only escape characters that fall outside the safe ASCII printable range (codes 32–126). This catches non-ASCII characters, control characters, and invisible formatting marks.
  3. Use your chosen numeric format—decimal or hex—for those non-ASCII escapes.

This gives you clean, readable output where only the characters that genuinely need escaping get entity-encoded.

When Does the Choice Between Hex and Decimal Actually Matter?

For the most part, it doesn't. Browsers handle both identically. The HTML5 parsing algorithm treats &#x... and &#... as two valid syntaxes for the same construct, and both resolve before the DOM is built. So functionally, in a modern browser, there is zero rendering difference.

But you're building a dev tool, not a browser. The differences that don't matter to Chrome absolutely matter to your users. Here are the real-world scenarios where the choice surfaces:

Regex and Pattern Matching

If someone writes a regex to detect numeric entities in your tool's output—and they will—decimal entities are easier to match cleanly. /&#(\d+);/g is a simple, readable pattern. Matching hex requires /&#x([0-9A-Fa-f]+);/gi with a case-insensitive flag, and if your tool emits mixed-case hex, the regex gets even more fragile. Decimal keeps downstream string processing predictable.

File Size and Minification

For most characters, hex produces shorter entities. The copyright symbol is &#169; (7 characters) versus &#xA9; (6 characters). For larger code points, the savings compound. &#x2019; is 8 characters; &#8217; is 8 characters—a tie. But &#x1F600; (the grinning face emoji) is 10 characters versus &#128512; at 11 characters. Across a document with hundreds of non-ASCII characters, hex wins on byte count. If your tool is outputting minified HTML for production, this is worth measuring.

Debugging and Developer Experience

When a developer pastes your tool's output into a debugger or a diff tool and sees &#x2019;, they can grep for 2019 and immediately find the Unicode entry. With &#8217;, they'd need to convert to hex first or search a decimal index. This is a small friction point, but if your tool is used in debugging workflows—as many escaping tools are—it adds up.

How Can You Test That Your Entity Escaping Works Across Browsers?

You've picked your format, implemented the pipeline, and now you need to verify it actually works. Here's a testing workflow I use for escaping tools.

Test the Round-Trip

The most important property of an escaper is round-trip fidelity: escape a string, then un-escape it, and you should get the original string back. Test this with a corpus that includes:

  • Basic ASCII (letters, digits, punctuation)
  • The five HTML-special characters
  • Common non-ASCII characters (em dash, curly quotes, accented letters)
  • Emoji and astral-plane characters (code points above 65535)
  • Zero-width characters and other invisible Unicode points
  • Surrogate pairs and combining character sequences

If your tool uses charCodeAt() instead of codePointAt(), the emoji test will catch it. An emoji like 😀 (U+1F600) will produce two separate surrogate entities instead of one correct entity, and the round-trip will fail.

Test in Multiple Rendering Contexts

Don't just test in Chrome. Drop your escaped output into:

  • An HTML file opened in Firefox and Safari
  • An XML document parsed with a strict XML parser
  • An SVG file rendered standalone
  • A textarea's value attribute (to test attribute-context escaping)
  • An email HTML template (some email clients have notoriously broken HTML parsers)

Email clients are where decimal entities earn their keep. Some older email clients—still in use in enterprise environments—don't correctly parse &#x... syntax. If your tool's output might end up in an email template, decimal is the safer bet. I've seen hex entities render as literal text in Outlook 2016 for characters above U+00FF. It's not a theoretical risk.

Verify with a Reference Implementation

Cross-check your tool's output against an established library like he (a popular HTML entity encoder/decoder for JavaScript). Encode the same input string with both your tool and the reference library, decode both, and confirm the decoded values match. The encoded strings don't need to be identical—different libraries make different format choices—but the decoded round-trip must produce the same characters.

Here's the bottom line for your dev tool: pick decimal as the default for maximum compatibility and downstream predictability. Switch to hex if you're optimizing for byte size, if your output targets XML/SVG ecosystems, or if developer readability of the code points matters more than universal parser compatibility. Document the choice in your tool's README so users know what to expect, and expose a configuration option if both formats are legitimate for your use case. That's the complete workflow—from understanding the difference to shipping a defensible default.

Frequently Asked Questions

What is the difference between hex and decimal HTML entities?

Both are used to escape special characters in HTML, but they use different numbering systems. Decimal entities use base-10 numbers (e.g., &#60;), while hexadecimal entities use base-16 numbers often prefixed with an 'x' (e.g., &#x3C;).

Should I use hex or decimal HTML entities?

It is entirely up to personal or project preference, as modern browsers decode both perfectly. However, hexadecimal entities are generally preferred by developers because they align directly with standard Unicode code point charts.

Are hex and decimal HTML entities rendered the same by browsers?

Yes, browsers render both types of entities identically as long as they reference the same character value. The browser simply parses the numerical value and outputs the corresponding Unicode character.

What is the syntax for hexadecimal HTML entities?

Hexadecimal HTML entities begin with an ampersand and a hash symbol, followed by an 'x' and the hex value, ending with a semicolon. For example, the copyright symbol is written as &#xA9;.

What is the syntax for decimal HTML entities?

Decimal HTML entities start with an ampersand and a hash symbol, followed by the base-10 number and a semicolon. Using the copyright symbol as an example, it is written as &#169;.

Do HTML entities prevent XSS attacks?

Yes, HTML entity encoding is a primary defense against Cross-Site Scripting (XSS) by neutralizing dangerous characters like angle brackets. Both hex and decimal formats are equally effective for escaping output and preventing malicious code execution.

How do I convert a Unicode code point to a hex HTML entity?

To convert a Unicode code point to a hex entity, drop the 'U+' prefix and append the remaining alphanumeric value to the '&#x' prefix. For example, U+0040 (the at symbol) becomes &#x40; in HTML.

Can I use HTML entities in JavaScript and CSS?

HTML entities are strictly parsed by the HTML parser, so they will not work directly inside JavaScript or CSS files. For JavaScript, you should use Unicode escapes like \u003C, and for CSS, use backslash escapes like \003C.

Why do we need to escape special characters in HTML?

Escaping special characters ensures that the browser displays them as visible text rather than interpreting them as HTML markup. This is crucial for rendering code snippets properly and maintaining application security.