Sugar High: The 1 kB Syntax Highlighter That Lets CSS Pick the Colors
If you write a blog, a documentation site, or anything that renders code, you have probably reached for a syntax highlighter and quietly winced at its weight. The popular options are powerful but heavy: they ship full grammar engines, bundled theme files, and sometimes a WebAssembly regex runtime, all to make a few code blocks readable. Sugar High, published as sugar-high, takes the opposite bet. It is a hand-written tokenizer for JavaScript, JSX, and TypeScript that fits in roughly a kilobyte, has zero runtime dependencies, and produces nothing but a plain HTML string of <span> elements.
The twist that makes it pleasant to live with is that Sugar High never decides what color anything should be. It only labels tokens with stable class names; the actual colors come from CSS custom properties you define yourself. That design makes it framework-agnostic, trivial to render on the server, and effortless to theme for dark mode. It was written by huozhi (Jiachi Liu), a Next.js core team member at Vercel, originally to highlight code on his own blog, which tells you exactly the niche it was built to nail: predominantly JavaScript and TypeScript content where bundle size and visual control matter more than supporting two hundred languages.
Why It Stands Out
A handful of deliberate choices set sugar-high apart from the usual suspects:
- It is genuinely tiny. The lean core is about 1 kB minified and gzipped. The full published library, with TypeScript support and the preset system, lands around 3 kB gzipped, still dramatically smaller than the alternatives.
- It has no framework dependency. Despite the JSX focus, it never imports React or touches the DOM. The output is a string, so you can inject it with
innerHTMLin vanilla JS ordangerouslySetInnerHTMLin React. - You own the theme. No CSS themes ship with the library. You set a handful of
--sh-*variables and the colors are entirely yours, which makes dark mode a second CSS block rather than a JavaScript theme switch. - It works at build time. Because the result is a static HTML string, you can highlight once during your build and ship plain HTML with zero client-side JavaScript.
- It is hand-tuned, not generic. There is no TextMate grammar or Oniguruma engine. A purpose-built tokenizer walks the source character by character, which is exactly why it stays so small.
The honest trade-off: the core deeply understands only JS, JSX, TS, and TSX. Other languages are handled by small approximate presets, so this is the wrong tool if you need exact, many-language fidelity. For that, reach for Shiki. For a JavaScript-centric blog, Sugar High is hard to beat.
Getting It Installed
Sugar High is ESM-only and has no peer dependencies, so installation is a single command.
npm install sugar-high
Or with yarn:
yarn add sugar-high
That is the entire footprint. There is no companion CSS file to import and no theme package to choose, because you will supply the colors yourself in a moment.
Highlighting Your First Snippet
The main entry point is highlight, which takes a string of code and returns a string of HTML. In a vanilla setup, you inject that HTML into a <code> element.
import { highlight } from "sugar-high";
const code = `const greet = (name: string) => \`Hello, \${name}!\``;
const codeHTML = highlight(code);
document.querySelector("pre > code")!.innerHTML = codeHTML;
In React, the same string drops straight into dangerouslySetInnerHTML. Because Sugar High produces escaped span markup for code content, this is the intended usage rather than a hack.
import { highlight } from "sugar-high";
function Code({ code }: { code: string }) {
return (
<pre>
<code dangerouslySetInnerHTML={{ __html: highlight(code) }} />
</pre>
);
}
At this point the code will render, but it will be uncolored. That is expected. Sugar High has wrapped each token in a span like sh__token--keyword, and now it is CSS's turn to bring the colors.
Painting With CSS Variables
Every token type maps to a --sh-* custom property. Define them once on an ancestor such as :root, and the whole document picks up your palette. The token types you can style are keyword, string, class, identifier, property, entity, jsxliterals, sign, and comment.
:root {
--sh-class: #2d5e9d;
--sh-identifier: #354150;
--sh-sign: #8996a3;
--sh-property: #0550ae;
--sh-entity: #249a97;
--sh-jsxliterals: #6266d1;
--sh-string: #00a99a;
--sh-keyword: #f47067;
--sh-comment: #a19595;
}
Dark mode requires no JavaScript at all. Because the colors live in CSS variables, you simply redefine them under a media query or a class selector and let the cascade do the work.
@media (prefers-color-scheme: dark) {
:root {
--sh-class: #4c97f8;
--sh-string: #0fa295;
--sh-keyword: #f47067;
--sh-comment: #6b7280;
}
}
This is the heart of Sugar High's appeal. The JavaScript stays the same forever; restyling your code blocks, supporting a theme toggle, or matching your brand is purely a CSS exercise.
Line Numbers and Highlighted Lines, No JavaScript Needed
When Sugar High generates output, it also wraps each line in a <span class="sh__line">. That single class unlocks line numbers and per-line emphasis entirely through CSS counters and selectors, with no extra library code.
pre code {
counter-reset: sh-line-number;
}
.sh__line::before {
counter-increment: sh-line-number 1;
content: counter(sh-line-number);
margin-right: 24px;
width: 1.5rem;
display: inline-block;
text-align: right;
color: #a4a4a4;
}
Want to draw attention to a particular line? Target it by its 1-based position. This is handy for tutorials where you want one line to glow.
.sh__line:nth-child(5) {
background: #f5f5f5;
}
Because the markup is static, these effects survive server rendering and work even with JavaScript disabled.
Tagging Lines From Code With lineClassName
CSS positional selectors are great when you know the line numbers ahead of time, but for dynamic content like diffs you want the highlighter to label lines based on their content. The lineClassName option lets you return a class for each line as it is generated, with no HTML post-processing.
import { highlight } from "sugar-high";
const html = highlight(code, {
lineClassName: (line, index) =>
line.startsWith("+") ? "added" : line.startsWith("-") ? "removed" : null,
});
Pair that with a couple of CSS rules and you have diff-style highlighting that you fully control.
.added {
background: rgba(46, 160, 67, 0.15);
}
.removed {
background: rgba(248, 81, 73, 0.15);
}
If you would rather not hand-roll diff handling, version 1.2.0 ships a diff preset alongside presets for c, css, go, java, python, and rust. A preset is just a bundle of options, so you spread it into highlight.
import { highlight } from "sugar-high";
import { rust } from "sugar-high/presets";
const html = highlight(source, { ...rust });
Keep in mind these non-JavaScript presets are heuristic approximations rather than full parsers, so they are excellent for the common case and may stumble on exotic edge cases.
Teaching It New Words
Sometimes you want the highlighter to treat a custom set of identifiers as keywords, perhaps the primitives of a library you are documenting. The keywords and typeKeywords options accept a Set of strings. Anything in typeKeywords is styled as the class token type and is checked before regular keywords.
import { highlight } from "sugar-high";
const html = highlight(code, {
keywords: new Set(["signal", "computed", "effect"]),
typeKeywords: new Set(["Signal", "Computed"]),
});
For deeper control, you can drop down to the tokenizer itself. tokenize returns an array of [tokenType, value] pairs, and the SugarHigh.TokenTypes map translates the numeric type into a readable name. This is the seam you would build on to render tokens into a custom component tree or to inspect how the parser sees your code.
import { tokenize, SugarHigh } from "sugar-high";
const tokens = tokenize(`const x = 1`);
tokens.forEach(([type, value]) => {
console.log(SugarHigh.TokenTypes[type], JSON.stringify(value));
});
Combined with the onCommentStart, onCommentEnd, and onQuote callbacks, these primitives are exactly how the bundled presets are built, which means you can author your own grammar for a language Sugar High does not yet ship.
A Few Things to Keep in Mind
Sugar High's minimalism comes with sharp edges worth knowing. Nothing renders in color until you supply the --sh-* variables, so a forgotten theme looks like a bug rather than a feature. The library is ESM-only, so CommonJS consumers need bundler interop. Output is injected as raw HTML, so while it produces escaped markup for code, you should still treat untrusted input with appropriate care. And because the core only truly parses JavaScript-family languages, any language without a preset will not highlight correctly.
For a blog, a documentation site, or an MDX pipeline that is mostly JavaScript and TypeScript, those constraints are easy to accept in exchange for a highlighter that weighs almost nothing, renders on the server for free, and hands you complete control over every color. Sugar High is not trying to be the universal answer. It is trying to be the perfect answer for the most common case, and it succeeds with remarkable economy.