Skip to content

Code Highlighter

<quiet-code-highlighter> stable since 6.0

Renders syntax-highlighted code using small, battle-tested grammars that load on demand. Reach for it whenever you want to show highlighted code blocks on the page.

Highlighting is powered by highlight.js , a mature library with grammars for virtually every language you'll encounter. The component works like a <pre> tag: put code inside it, tell it the language, and it renders an accessible, themeable code block. HTML, CSS, JavaScript, and TypeScript are available instantly, while other grammars load on demand from the library's assets, so you never fetch grammars you don't use.

<button type="button" class="primary" disabled> <quiet-icon name="rocket"></quiet-icon> Launch </button>
<quiet-code-highlighter language="html">
  &lt;button type="button" class="primary" disabled&gt;
    &lt;quiet-icon name="rocket"&gt;&lt;/quiet-icon&gt;
    Launch
  &lt;/button&gt;
</quiet-code-highlighter>

Like a <pre> tag, the code renders exactly as written. Content is automatically dedented, so feel free to indent it naturally in your markup. If you need to preserve indentation, add the without-dedent attribute.

Examples Jump to heading

Setting the language Jump to heading

Set the language attribute to the language the code is written in. Common aliases work too, e.g. js, ts, html, py, and php. If you don't set a language, the code renders as plain text. See this document for available languages.

<?php function greet(string $name = 'world'): bool { echo "Hello, {$name}!"; return true; }
<quiet-code-highlighter language="php">
  &lt;?php
  function greet(string $name = 'world'): bool {
    echo "Hello, {$name}!";
    return true;
  }
</quiet-code-highlighter>

Escaping markup Jump to heading

Because the browser parses your HTML before the component sees it, encode your code the way you would for any HTML: replace < with &lt;, & with &amp;, and > with &gt;. This is exactly what a server-side escaping function does, and it renders correctly no matter what the code contains. When encoding by hand gets tedious, set the code with the code property instead, which renders verbatim.

You can often skip encoding, however. A < only causes trouble when it's immediately followed by something tag-like, as in generics (Map<string, User>) or literal markup, and a & only when it forms a character reference (&amp;, &copy;). A < used as a comparison (x < 100) or a standalone & (a && b) passes through untouched. Relying on this keeps your code readable, but it's on you to catch the cases that need encoding.

const users: Map<string, User> = new Map(); // Comparisons are fine without escaping if (users.size < 100) { users.set('cat', { name: 'Whiskers' }); }
<quiet-code-highlighter language="ts">
  const users: Map&lt;string, User> = new Map();

  // Comparisons are fine without escaping
  if (users.size < 100) {
    users.set('cat', { name: 'Whiskers' });
  }
</quiet-code-highlighter>

If the component finds HTML elements in its content, it logs a helpful warning to the console because it usually means a < slipped through unescaped. When escaping gets tedious, set the code with the code property instead.

HTML formatters and minifiers know to leave <pre> alone, but they don't know about custom elements, so they may collapse the whitespace inside this one. If your toolchain reformats HTML, exclude these elements from processing, e.g. <!-- prettier-ignore -->, or use the code property.

Setting code programmatically Jump to heading

Set the code property to highlight code from JavaScript. When set, it always wins over slotted content and renders verbatim, without dedenting. This is the escape hatch for dynamic code and for frameworks where escaping is impractical.

<quiet-code-highlighter id="code-highlighter__dynamic" language="css"></quiet-code-highlighter>

<quiet-text-area
  id="code-highlighter__dynamic-input"
  label="CSS"
  value=".cat {&#10;  color: gray;&#10;  cursor: pointer;&#10;}"
  rows="4"
  style="margin-block-start: 1rem;"
></quiet-text-area>

<script type="module">
  import { allDefined } from '/dist/quiet.js';

  await allDefined();

  const highlighter = document.getElementById('code-highlighter__dynamic');
  const input = document.getElementById('code-highlighter__dynamic-input');

  highlighter.code = input.value;

  input.addEventListener('quiet-input', () => {
    highlighter.code = input.value;
  });
</script>

Loading additional languages Jump to heading

Grammars for hundreds of languages beyond HTML, CSS, JavaScript, and TypeScript load automatically the first time they're used. They're fetched from the library's assets/grammars folder using the library path, the same way icons load. While a grammar loads, the code shows as plain text and the loading custom state is applied. Colors appear as soon as it arrives.

/// A cat that collects treats. struct Cat { name: String, treat_count: u32, } impl Cat { fn new(name: &str) -> Self { Self { name: name.into(), treat_count: 0 } } fn nibble(&mut self) { self.treat_count += 1; } }
<quiet-code-highlighter language="rust">
  /// A cat that collects treats.
  struct Cat {
      name: String,
      treat_count: u32,
  }

  impl Cat {
      fn new(name: &str) -> Self {
          Self { name: name.into(), treat_count: 0 }
      }

      fn nibble(&mut self) {
          self.treat_count += 1;
      }
  }
</quiet-code-highlighter>

If a grammar can't be found, the component dispatches the quiet-load-error event, applies the error custom state, and renders the code as plain text.

Registering custom grammars Jump to heading

Use the registerCodeGrammar() function to add languages the library doesn't ship, using the same language definition format highlight.js uses. Registered grammars take effect immediately, even for code highlighters already on the page.

order tuna 12 order salmon 3 # cats prefer chicken order chicken 999
<quiet-code-highlighter id="code-highlighter__custom" language="treatlang">
  order tuna 12
  order salmon 3
  # cats prefer chicken
  order chicken 999
</quiet-code-highlighter>

<script type="module">
  import { registerCodeGrammar } from '/dist/quiet.js';

  registerCodeGrammar('treatlang', () => ({
    name: 'Treatlang',
    contains: [
      { scope: 'comment', begin: /#/, end: /$/ },
      { scope: 'keyword', begin: /\border\b/ },
      { scope: 'number', begin: /\b\d+\b/ }
    ]
  }));
</script>

Changing the color scheme Jump to heading

Code blocks follow the surrounding theme by default, rendering light until they're inside a quiet-dark container. Set the color-scheme attribute to light or dark to force one, e.g. for a dark code block on an otherwise light page.

/* Always light */ .cat { color: gray; } /* Always dark */ .cat { color: gray; }
<quiet-code-highlighter language="css" color-scheme="light">
  /* Always light */
  .cat {
    color: gray;
  }
</quiet-code-highlighter>

<quiet-code-highlighter language="css" color-scheme="dark">
  /* Always dark */
  .cat {
    color: gray;
  }
</quiet-code-highlighter>

Preserving indentation Jump to heading

Slotted code is dedented automatically, so indenting it to match your markup doesn't leak into the output. Leading and trailing blank lines are removed, too. Add the without-dedent attribute to opt out, giving you <pre> semantics where a single newline after the opening tag is ignored and everything else is preserved. As with <pre>, place the closing tag right after the last character to avoid rendering trailing whitespace.

This line keeps its four leading spaces. This one keeps six.
<quiet-code-highlighter language="plaintext" without-dedent>
    This line keeps its four leading spaces.
      This one keeps six.</quiet-code-highlighter>

Wrapping long lines Jump to heading

By default, long lines cause the code block to scroll horizontally. Add the wrap attribute to soft-wrap them instead.

curl -X POST https://api.example.com/v1/treats/orders -H "Content-Type: application/json" -d '{"flavor": "tuna", "quantity": 3, "notes": "For the cats in the server room"}'
<quiet-code-highlighter language="bash" wrap>
  curl -X POST https://api.example.com/v1/treats/orders -H "Content-Type: application/json" -d '{"flavor": "tuna", "quantity": 3, "notes": "For the cats in the server room"}'
</quiet-code-highlighter>

Changing the label Jump to heading

The code block is exposed to assistive devices as a region labeled "Code" in the configured language. Use the label attribute to provide a more descriptive one.

{ "name": "Whiskers", "occupation": "Nap supervisor", "treats": ["tuna", "salmon"] }
<quiet-code-highlighter language="json" label="Example API response">
  {
    "name": "Whiskers",
    "occupation": "Nap supervisor",
    "treats": ["tuna", "salmon"]
  }
</quiet-code-highlighter>

Removing the copy button Jump to heading

A copy button appears in the top corner when you hover over the code block. It always copies the code being displayed, whether it comes from the slot or the code property. Add the without-copy attribute to remove it.

SELECT name, favorite_treat FROM cats WHERE mood = 'sleepy' ORDER BY naps DESC;
<quiet-code-highlighter language="sql" without-copy>
  SELECT name, favorite_treat FROM cats WHERE mood = 'sleepy' ORDER BY naps DESC;
</quiet-code-highlighter>

Styling code blocks Jump to heading

In dark mode, syntax colors match the palette the Quiet docs use for code. Use the custom properties to change token colors and the ::part(pre) part to restyle the container. Provide colors with light-dark() to theme both modes at once, or plain colors to theme them uniformly.

For styles beyond color, every token exposes a token-* CSS part named after the custom property that colors it, e.g. ::part(token-comment) for italic comments. All tokens also carry the token part, so you can target every token at once.

This example recreates Dracula in dark mode and its official light counterpart, Alucard, in light mode. It also gives the container a thicker border and a different font, and uses the token parts to apply Dracula's signature italics to attributes and parameters.

// It's just a phase, mom const cat = { name: 'Count Whiskula', age: 3, findTreat() { return ['tuna', 'salmon'].at(Math.floor(Math.random() * 2)); } };
<quiet-code-highlighter language="js" class="code-highlighter__dracula">
  // It's just a phase, mom
  const cat = {
    name: 'Count Whiskula',
    age: 3,
    findTreat() {
      return ['tuna', 'salmon'].at(Math.floor(Math.random() * 2));
    }
  };
</quiet-code-highlighter>

<style>
  .code-highlighter__dracula {
    --attribute-color: light-dark(#14710a, #50fa7b);
    --comment-color: light-dark(#635d97, #6272a4);
    --deletion-color: light-dark(#cb3a2a, #ff5555);
    --function-color: light-dark(#14710a, #50fa7b);
    --keyword-color: light-dark(#a3144d, #ff79c6);
    --number-color: light-dark(#644ac9, #bd93f9);
    --punctuation-color: light-dark(#1f1f1f, #f8f8f2);
    --string-color: light-dark(#846e15, #f1fa8c);
    --tag-color: light-dark(#a3144d, #ff79c6);
    --variable-color: light-dark(#a34d14, #ffb86c);

    &::part(pre) {
      border: solid 2px light-dark(#644ac966, #bd93f966);
      border-radius: var(--quiet-border-radius-md);
      background-color: light-dark(#fffbeb, #282a36);
      color: light-dark(#1f1f1f, #f8f8f2);
      font-family: 'Code New Roman', 'Cascadia Code', 'JetBrains Mono', monospace;
    }

    &::part(copy) {
      margin-inline-end: 2px;
      margin-block-start: 2px;
    }

    &::part(token-attribute),
    &::part(token-variable) {
      font-style: italic;
    }

    &::part(copy__copy-button__button) {
      background-color: light-dark(#fffbeb, #282a36);
      color: light-dark(#1f1f1f, #f8f8f2);
    }
  }
</style>

Keyboard support Jump to heading

When code overflows its container, tab to the code block to focus it, then use the keys below.

Key Action
Arrow Keys Scrolls the code in the pressed direction
Home Scrolls to the beginning of the code
End Scrolls to the end of the code

API Jump to heading

Importing Jump to heading

The autoloader is the recommended way to import components but, if you prefer to do it manually, the following code snippets will be helpful.

CDN Self-hosted

To manually import <quiet-code-highlighter> from the CDN, use the following code.

import 'https://cdn.quietui.org/v5.3.1/components/code-highlighter/code-highlighter.js';

To manually import <quiet-code-highlighter> from a self-hosted distribution, use the following code. Remember to replace /path/to/quiet with the appropriate local path.

import '/path/to/quiet/components/code-highlighter/code-highlighter.js';

Slots Jump to heading

Code Highlighter supports the following slots. Learn more about using slots

Name Description
(default) The code to highlight. Like the content of a <pre> tag, it renders exactly as written, so encode < as &lt; and & as &amp; when the code contains markup or character references. Slotted code is dedented automatically unless without-dedent is set.

Properties Jump to heading

Code Highlighter has the following properties that can be set with corresponding attributes. In many cases, the attribute's name is the same as the property's name. If an attribute is different, it will be displayed after the property. Learn more about attributes and properties

Property Description Reflects Type Default
code The code to highlight. When set, it takes precedence over slotted content and renders verbatim, without dedenting. Use when the code is dynamic or when escaping slotted content is impractical. string
language The language the code is written in. Any grammar name or alias that ships with the library works, e.g. html, css, js, and ts are available immediately and others such as python or rust load on demand. string 'plaintext'
label An accessible label for the code region. If omitted, a localized fallback is used instead. string ''
colorScheme
color-scheme
The color scheme of the code block. When auto, the scheme is inherited from the surrounding theme, i.e. light by default and dark inside a quiet-dark container. Use light or dark to force one. 'light' | 'dark' | 'auto' 'auto'
withoutCopy
without-copy
Removes the copy button. boolean false
withoutDedent
without-dedent
Preserves slotted code exactly as written instead of dedenting it, matching the behavior of a <pre> tag. A single newline immediately after the opening tag is ignored and all other whitespace is kept, including trailing whitespace. Has no effect on the code property, which always renders verbatim. boolean false
wrap Wraps long lines instead of showing a horizontal scrollbar. boolean false

Events Jump to heading

Code Highlighter dispatches the following custom events. You can listen to them the same way was native events. Learn more about custom events

Name Description
quiet-loaded Emitted when a grammar has been loaded and the code has been highlighted with it.
quiet-load-error Emitted when a grammar fails to load. The code is rendered as plain text in this case.

CSS custom properties Jump to heading

Code Highlighter supports the following CSS custom properties. You can style them like any other CSS property. Learn more about CSS custom properties

Name Description Default
--attribute-color The color of attribute names and selectors. light-dark(#3a7a2a, #9bc789)
--comment-color The color of comments. light-dark(#7c7d87, #a4a6b0)
--deletion-color The color of deletions and symbols. light-dark(#c13b6f, #eea3bb)
--function-color The color of function, class, and section titles. light-dark(#b25d09, #e4b073)
--keyword-color The color of keywords, literals, and built-ins. light-dark(#315bc4, #98bafe)
--number-color The color of numbers and regular expressions. light-dark(#b25d09, #e4b073)
--punctuation-color The color of operators and punctuation. light-dark(#7c7d87, #a4a6b0)
--string-color The color of strings. light-dark(#3a7a2a, #9bc789)
--tag-color The color of tags and tag names. light-dark(#315bc4, #98bafe)
--variable-color The color of variables, properties, and parameters. light-dark(#b25d09, #e4b073)

CSS parts Jump to heading

Code Highlighter exposes internal elements that can be styled with CSS using the selectors shown below. Learn more about CSS parts

Name Description CSS selector
pre The <pre> element that acts as the scroll container. ::part(pre)
code The <code> element that contains the highlighted markup. ::part(code)
token Any highlighted token. Every token carries this part in addition to its token-* part, so use it to style all tokens at once. ::part(token)
token-attribute Attribute names and selectors. ::part(token-attribute)
token-comment Comments. ::part(token-comment)
token-deletion Deletions and symbols. ::part(token-deletion)
token-function Function, class, and section titles. ::part(token-function)
token-keyword Keywords, literals, and built-ins. ::part(token-keyword)
token-number Numbers and regular expressions. ::part(token-number)
token-punctuation Operators and punctuation. ::part(token-punctuation)
token-string Strings. ::part(token-string)
token-tag Tags and tag names. ::part(token-tag)
token-variable Variables, properties, and parameters. ::part(token-variable)
copy The copy button, a <quiet-copy> element. ::part(copy)
copy__copy-button The copy button's copy-button part. ::part(copy__copy-button)
copy__copy-button__button The copy button's copy-button__button part. ::part(copy__copy-button__button)
copy__copy-icon The copy button's copy-icon part. ::part(copy__copy-icon)
copy__feedback The copy button's feedback part. ::part(copy__feedback)

Custom States Jump to heading

Code Highlighter has the following custom states. You can target them with CSS using the selectors shown below. Learn more about custom states

Name Description CSS selector
loading Applied while a grammar is being fetched. :state(loading)
error Applied when the grammar failed to load or resolve. :state(error)

Dependencies Jump to heading

Code Highlighter automatically imports the following elements. Sub-dependencies are also included in this list.

Search this website Toggle dark mode View the code on GitHub Follow @quietui.org on Bluesky Follow @quiet_ui on X

    No results found