Published Apr 17, 2023

Last Updated May 24, 2023

Doing it Wrong #1: Using CSS to Render Content

Categories: Doing It Wrong

Tags: #CSS , #SASS , #DOM , #Accessibility

It should go without saying, if you’re adding blocks of written content to a page using CSS, you’re “doing it wrong”.

But since we’re just having fun here, why not? Also, I’ll be using Sass right from the beginning to save some keystrokes.

Initial idea

At first I thought I’d just style a bunch of HTML tags with content and be done with it. That’s how that property works, right?

So this was more-or-less my initial solution:

.event {
  .title { content: "Awesome Regional Conference 2023" }
  .date { content: "5/21/2023" }
  .venue { content: "Totally Real Conference Hall" }
  .location { content: "123 Totally Real Blvd, Austin, Tx" }

And I thought it was done. Let’s throw this into JSFiddle.

Wow, who else didn’t see that coming…? Really, only me…?

If you use content a lot, you probably saw this coming. I obviously don’t. So first I confirmed it was being set on the elements I expected it to be set on.

Then I started messing around with styles: margins, dimensions, font sizes and colors etc.

After scratching my head for a little while, I remembered that the only times I actually had used content were on ::before and ::after. So I gave that a shot.

And look at that, it worked right away!

I suppose it makes sense that content can’t work directly on the main content area of an HTML element. That would probably break its actual HTML and text content. In hindsight, I should’ve learned how content worked sooner when messing with ::before and ::after styles defined by tools like Bootstrap and FontAwesome.

Taking it further

Ok, so now we’re rendering written content into HTML via CSS. How can this be a bit more reusable? With CSS variables!

Awesome! So now we can generate styles for a specific event ID and it’ll work.

But we’re also making things a bit repetitive now. Imagine if you had 10-20 elements like this with matching vars. Blech.

Mixins to the rescue!

Since Sass has mixins, this’ll help us clean up a bit more (I know, this is all wrong and messy anyway, but humor me). If we write a mixin name—say, before—we can automatically generate all of the classes and look for the right CSS vars, just with a list of strings.

@mixin before($labels...) {
    @for $i from 0 to length($labels) {
        .#{nth($labels, $i + 1)}::before { content: var(--#{nth($labels, $i + 1)}) }

Using the $var... syntax in the mixin signature, we can condense all arguments to the mixin into 1 list.

If we had 2 params instead of 1, then it would work more like this:

@mixin before($param1, $param2...) {}

// here, $param1 gets the first argument, and $param2 gets all
// other arguments after that: "something else" and
// "something else again".
@include before("something", "something else", "something else again")

Then using the @for loop above combined with length($labels), we can loop over the whole list. And using nth, we can use each string in the list as both the class selector and CSS var name.

Some things to note about @for and nth()

You’ll notice the range in the loop is basically 0 to length($labels). This works how you’d expect most for loops to work: start at 0, stop when you reach length.

But lists in Sass are actually indexed starting at 1. So there is no “element 0”, and the last element’s index number is the same as the length of the list.

This forces us to do a little extra math in 1 of 2 places: the loop definition:

@for $i from 1 to length($labels) + 1 {}

… or the loop body, like above: $i + 1.

Ahh, nice and clean

Here’s the working version of this.

But wait, there’s more…

What if we wanted to do something even crazier, like adding extra content into our HTML?

Oh right, we’re using ::before, so our CSS content is literally before the actual HTML content.

Let’s switch that around a bit: rename the mixin and use the ::after pseudo-element.

And there it is, the final version of our backwards little CSS project.


This is a given, but there are problems with doing this unironically.

First, content set in content does not exist in the DOM. This makes getting the content back out very difficult and hurts accessibility.

Next, it adds overhead where none is needed. CSS is for changing the appearance of content, not rendering content directly.

Lastly, along with that last point, it couples entire content placements and even parts of your frontend implementation to your stylesheet. Your frontend should not need to use your stylesheet to render content when it’s already rendering everything else.

What was learned?

For starters, This type of thing is insane to do on a serious project, so maybe don't—unless you do code golf. Do be crazy in code golf. :wink:

More practically:

  1. If you don't use content much (raising my hand :raised_hand:), you learned it doesn't do anything to HTML elements that actually have their own content.

  2. Why it doesn't affect HTML content is important: what it renders is outside the DOM, which can negatively impact your overall user experience, especially for accessibility.

Thanks for reading!

Please confirm whether you would like to allow tracking cookies on this website, in accordance with its privacy policy.