DEV Community

Cover image for Syntax-Highlight CSS with Semantic HTML — and get Dark Mode for free
Mads Stoumann
Mads Stoumann

Posted on

Syntax-Highlight CSS with Semantic HTML — and get Dark Mode for free

When we syntax highlight CSS-code, we typically use a JavaScript library, that turns the CSS into a bunch of <span>-tags with different class’es.

I thought it could be fun to HTML’ify the CSS and turn it into meaningful semantics. But which tags should represent a CSS rule?

CSS is written as rules, which consist of a selector group and a declaration block.

The latter contains one or more property/value-pairs.

Example:

.selector {
  /* declaration block */
  property: value;
}
Enter fullscreen mode Exit fullscreen mode

A stylesheet — at least to me — is like an unordered list (<ul>), with each item (<li>) representing a rule.

Any heading tag (<h2>, <h3> etc.) will work as the selector group — and a description list (<dl>) is the perfect candidate for the declaration block.

A CSS property will be a term, using the <dt>-tag, and each CSS value will be a description, using the <dd>-tag.

The <var>-tag will be used for CSS Custom Properties.

Example:

<ul>
  <li>
    <h3>body</h3>
    <dl>
      <dt>accent-color</dt>
      <dd><var>AccentColor</var></dd>
      <dt>background-color</dt>
      <dd>Canvas</dd>
      <dt>color</dt>
      <dd><var>ColorGray-80</var></dd>
      <dt>color-scheme</dt>
      <dd>light dark</dd>
      <dt>font-family</dt>
      <dd><var>ff-sans</var></dd>
      <dt>font-size</dt>
      <dd>clamp(1rem, 0.8661rem + 0.4286vw, 1.1875rem)</dd>
      <dt>line-height</dt>
      <dd>1.5</dd>
      <dt>margin-inline</dt>
      <dd>auto</dd>
      <dt>max-inline-size</dt>
      <dd>70ch</dd>
      <dt>padding-inline</dt>
      <dd>2ch</dd>
    </dl>
  </li>
  <li>
    <h3>img</h3>
    <dl>
      <dt>height</dt>
      <dd>auto</dd>
      <dt>max-width</dt>
      <dd>100%</dd>
  </li>
  <li>
    <h3>td</h3>
    <dl>
      <dt>border</dt>
      <dd>1px solid</dd>
      <dt>font-size</dt>
      <dd>smaller</dd>
      <dt>padding</dt>
      <dd>1ch</dd>
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

Out-of-the-box this doesn't look particularly great:

Syntax Highlight without styles

However, with very few lines of CSS, we can turn that into:

Syntax Highlight Light Mode

... and by adding just one CSS-declaration:

.dark {
  color-scheme: dark;
}
Enter fullscreen mode Exit fullscreen mode

— we get:

Syntax Highlight Dark Mode


Styling the syntax highlight

First, we need some variables for the colors and basic CSS for the list itself:

ul {
  --_property: color-mix(in srgb, HighLight, CanvasText 30%); 
  --_selector: cornflowerblue;
  --_value: orange;

  background: color-mix(in srgb, Canvas 95%, CanvasText 5%);
  font-family: ui-monospace,  monospace;
  list-style: none;
  margin: 0;
  overflow: auto;
  padding: 2ch;
}
Enter fullscreen mode Exit fullscreen mode

The color-mix-method is used here with system colors. These will automatically change in dark mode — more on that later.

Next, using native CSS nesting, we make sure that all the child-tags behave as we want:

ul {
  & * {
    font-size: 1em;
    font-style: normal;
    font-weight: 400;
    margin: 0;
    white-space: nowrap;
  }
}  
Enter fullscreen mode Exit fullscreen mode

For the selector, we allow <em>, <strong> or any <h>eading-tag. We add a {-char after, and after each rule (<li>), we add } and a line-break:

ul {
  & :is(em, h2, h3, h4, h5, h6, strong) {
    color: var(--_selector);
    &::after { content: " {"; }
  }
  & li::after {
    color: var(--_selector);
    content: "}\a"; white-space: pre;
  }
}
Enter fullscreen mode Exit fullscreen mode

For the <var>iables, we add:

ul {
  & var {
    &::before { content: "var(--"; }
    &::after { content: ")"; }
  }
}
Enter fullscreen mode Exit fullscreen mode

We want to display the property/value-pairs as inline, so they don't break to separate lines. We add the :-char after each property — and for the value, we want to end the line with a ;-char and a line-break:

ul {
  & dd, & dt { display: inline; }
  & dd {
    color: var(--_value);
    &::after {
      content: ";\a";
      white-space: pre;
    }
  }
  & dl { margin: 0 0 0 2ch; }
  & dt {
    color: var(--_property);
    &::after { content: ":"; }
  }
}
Enter fullscreen mode Exit fullscreen mode

And that's it!


Dark Mode for free

Because we used system colors, we get dark mode for free.

Add color-scheme: light dark; to your body-styles, and change your OS to either light or dark mode, and you should see the syntax-styles update.

The great thing about color-scheme, is that you can force it to one or the other, even if your OS is set to the opposite.

If your OS is set to "light mode" and you set color-scheme: dark on the <ul>-tag, it'll render as "dark mode", even if the rest of the page is in "light mode".


Code

For your reference, here's the complete chunk of CSS — give it a meaningful class-name instead of ul:

ul {
  --_property: color-mix(in srgb, HighLight, CanvasText 30%); 
  --_selector: cornflowerblue;
  --_value: orange;

  background: color-mix(in srgb, Canvas 95%, CanvasText 5%);
  font-family: ui-monospace, monospace;
  list-style: none;
  margin: 0;
  overflow: auto;
  padding: 2ch;
  & * {
    font-size: 1em;
    font-style: normal;
    font-weight: 400;
    margin: 0;
    white-space: nowrap;
  }
  & dd, & dt { display: inline; }
  & dd {
    color: var(--_value);
    &::after {
      content: ";\a";
      white-space: pre;
    }
  }
  & dl { margin: 0 0 0 2ch; }
  & dt {
    color: var(--_property);
    &::after { content: ":"; }
  }
  & :is(em, h2, h3, h4, h5, h6, strong) {
    color: var(--_selector);
    &::after { content: " {"; }
  }
  & li::after {
    color: var(--_selector);
    content: "}\a"; white-space: pre;
  }
  & var {
    &::before { content: "var(--"; }
    &::after { content: ")"; }
  }
}
Enter fullscreen mode Exit fullscreen mode

NOTE: The code use native nesting and color-mix, which are currently behind-a-flag or in "nightly"-versions of browsers.


Cover Image by DALL·E, using the prompt:

Using the terms "cascading style sheets" and "syntax highlight", generate a painting in the style of Salvador Dali

Top comments (2)

Collapse
 
grahamthedev profile image
GrahamTheDev • Edited

You inspired me!

How about this as a way to get rid of the <span> madness and let people just use a <pre> element for code?

About to drop an article on this, JavaScript syntax highlighting...using a single element, no <span> element and just CSS.

And here is the generator to create the gradient background that you would actually serve up:

Collapse
 
madsstoumann profile image
Mads Stoumann

That’s pretty funky! 🤓