Syntax Highlighting in React Applications Using Sugar High

2024-05-01
By: O Wolfson

A powerful tool for achieving syntax highlighting in React applications is sugar-high, a lightweight syntax highlighter. In this article, we'll explore how sugar-high can be integrated into a React and Next.js environment using MDX to render markdown content dynamically.

What is sugar-high?

sugar-high is a JavaScript library designed to apply syntax highlighting to code snippets. It parses the text of the code and wraps different parts of the syntax in HTML elements with class names corresponding to their syntactic role, such as keywords, strings, or comments. These classes can then be targeted with CSS to apply colors and styles, bringing the static code to life.

Integrating sugar-high with React, Next.js, and MDX

To demonstrate the integration, consider a scenario where we want to create a React component that automatically highlights code snippets within MDX content. MDX is an extension of markdown that supports JSX and can be used in React applications, allowing markdown and React components to coexist seamlessly.

Here is an example setup:

tsx
import { MDXRemote } from "next-mdx-remote/rsc";
import { highlight } from "sugar-high";
import React from "react";

interface CodeProps {
  children: React.ReactNode;
}

function Code({ children }: CodeProps) {
  let codeHTML = highlight(children as string);
  return (
    <div className="bg-gray-800 p-4 rounded-lg">
      <code
        dangerouslySetInnerHTML={{ __html: codeHTML }}
        className="block w-full overflow-x-auto"
      />
    </div>
  );
}

const components = { code: Code };

export default function CustomMDX(props: any) {
  return <MDXRemote {...props} components={components} />;
}

How sugar-high Works

The Code component takes any children passed to it—typically text containing code—and uses sugar-high's highlight function. This function analyzes the code and identifies various elements like keywords, operators, and literals. It then wraps these elements in <span> tags with appropriate classes. The resultant HTML string represents the original code but enhanced with markup for styling.

The dangerouslySetInnerHTML property in React is used to inject this HTML into the component's DOM, allowing the pre-styled code to render with the desired syntax highlighting. Here’s a breakdown of how the highlight function transforms the code:

  • Tokenization: sugar-high breaks down the raw text into tokens based on the language syntax rules. Each token type (keyword, string, operator) is identified.
  • Styling: Each token is wrapped in a <span> tag with a class that corresponds to its type, such as token-keyword for JavaScript keywords.
  • Rendering: The processed string is then safely injected into the DOM, where CSS styles corresponding to each token class are applied.

Why Use sugar-high?

sugar-high is particularly beneficial due to its simplicity and effectiveness. It doesn't require loading large language-specific grammars, making it lighter than some other highlighting libraries. Moreover, sugar-high offers:

  • Ease of Integration: As seen in the example, integrating sugar-high with React and MDX is straightforward.
  • Customizable: Developers can easily customize the styles applied to different types of syntax.
  • Performance: It is performant enough for most typical use cases, providing quick parsing and rendering without significant overhead.

Customizing the Syntax Highlighting

It's crucial to pair it with custom CSS that targets the classes generated by the highlighter. The CSS snippet provided in the setup specifies colors for various syntax elements, ensuring that each part of the code stands out distinctly, improving both readability and visual appeal.

Here's how the CSS works in synergy with sugar-high:

Class-Specific Colors: Each span element generated by sugar-high is assigned a class based on the type of syntax it represents (e.g., .token-keyword, .token-string). The CSS rules apply specific colors to these classes. For instance, keywords are colored with a bright red (#ff6b6b), while strings appear in a lighter teal (#26dabd). This makes different elements of the code easily distinguishable at a glance.

Customizability: The CSS variables (--sh-class, --sh-string, etc.) allow developers to quickly adjust the color scheme without altering the structure of the CSS. This modularity and ease of customization make it straightforward to adapt the syntax highlighting to fit the theme of any site or application.

Here's an example of how the CSS might look in global styles (globals.css or similar):

css
:root {
  --sh-class: #4d9ef6; /* Brighter blue for class */
  --sh-identifier: #6caedd; /* Lighter blue for identifiers */
  --sh-sign: #cdd3de; /* Lighter gray to ensure signs are visible */
  --sh-property: #3182ce; /* Brighter blue for properties */
  --sh-entity: #2db1bc; /* Lighter teal for entities */
  --sh-jsxliterals: #939dfd; /* Lighter purple for JSX literals */
  --sh-string: #26dabd; /* Lighter teal for strings */
  --sh-keyword: #ff6b6b; /* Brighter red for keywords */
  --sh-comment: #cbc3c3; /* Lighter gray for comments */
}

h1 {
  @apply text-3xl font-bold text-gray-600; /* Tailwind classes for size, weight, and color */
}

p {
  @apply text-base text-gray-300;
}

a {
  @apply text-blue-400 hover:text-blue-600;
  transition: color 0.3s;
}