Styling Comment Threads

Avatar of Tahmid
Tahmid on

Comment threads are one of those things that look really simple when executed right. When designing them yourself, you may find that they are rather deceptively simple. There is a lot that goes into designing nice and usable comment threads, and in this article, I will try my best to walk you through the steps to building a comment thread, that is great to look at, and a joy to use.

What makes a good comment thread?

Before diving into designing and writing code, let’s break down what actually makes a good comment thread. A good comment thread will have the following characteristics:

  1. Comments are readable, and all the important data-points are visible to the user at a glance. This includes the author, number of votes, timestamp, and the content.
  2. Different parts of comments are visually distinct, so that the user is immediately able to understand what is what. This is especially important for the action buttons (e.g. reply, report), and links.
  3. There must be visual cues for hierarchies between the comments. This is a requirement for having nested comments, i.e. one comment being a reply to another one.
  4. There should be an easy way to quickly scroll to a comment, especially to the parent comment from its children (i.e. replies).
  5. Comments should also have a toggle feature which allows the users to hide and show the comment and its replies.

As you can see, that’s quite a lot to consider! There are also some nice-to-haves that we won’t cover in this article, but are certainly good enhancements:

  1. Users should be able to flag a comment so a moderator is aware of inappropriate content.
  2. A comment can be up- or down-voted as a way of signaling helpful and unhelpful comments.
  3. Only the first few comments are displayed and the user is able to load more comments.

The above features would require at least a bit of JavaScript to pull off. Moreover, depending on the tech stack, these features could just as likely be implemented server side, especially keeping track of the number of votes and flag status of comments. That is why we will focus only on styling the comment threads in this article. With that out of the way, let’s knock out the first set of points and design our comment thread.

A basic comment thread

A comment by itself has a pretty simple structure. Here’s a skeleton of a single comment:

A single comment structure

So far, so good. Notice how the replies have an extra margin to the left. This is meant to satisfy the visual hierarchy (point #3 above). The markup for the above structure could look something like this:

<div class="comment">

  <!-- Comment heading start -->
  <div class="comment-heading">

    <!-- Comment voting start -->
    <div class="comment-voting">
      <button type="button">Vote up</button>
      <button type="button">Vote down</button>
    </div>
    <!-- Comment voting end -->

    <!-- Comment info (author, # of votes, time added) start -->
    <div class="comment-info">
      <a href="#" class="comment-author">{{ author }}</a>
      <p>
        {{ # of votes }} • {{ time_added }}
      </p>
    </div>
    <!-- Comment info (author, # of votes, time added) end -->
  </div>
  <!-- Comment heading end -->

  <!-- Comment body start -->
  <div class="comment-body">
    <p>
      {{ comment_content }}
    </p>
    <button type="button">Reply</button>
    <button type="button">Flag</button>
  </div>
  <!-- Comment body end -->

  <!-- Replies start -->
  <div class="replies">
    <!-- ... -->
  </div>
  <!-- Replies end -->

</div>

There is nothing to explain here — just a bunch of containers with some basic elements. So instead, let’s look at a real-life example, with a comment that also has a reply.

Looks pretty nice, right? The above example satisfies the first three points, so we are already on our way. A few things to understand about the code above:

  • The replies are given an extra margin to the left side to provide a visual cue of the hierarchy.
  • The vote up and vote down buttons are using HTML character codes for the icons.
  • The class .sr-only hides elements on all devices except on screen readers. This makes the voting buttons accessible. If you are using a framework, like Bootstrap or Halfmoon, this class comes automatically packed in.

Now that we have a basic comment thread going, let’s add a feature to help users quickly scroll to a comment. As mentioned above, this is especially useful when someone wants to jump to the parent comment of a reply.

In order to build it, we need to decide what these links look like. While this is entirely subjective, one particular design I really like is a clickable “border” on the left hand side of the comment, something like this:

Link (marked in red) that allows users to jump to a comment

In order to accommodate the link, we are pushing the comment body to the right and aligning it with the replies. This design also has the added benefit of reinforcing the hierarchy between the comments, because you can simply look at the number of border links to the left and determine the level of nesting of the comment you are currently reading. And of course, you can also immediately jump to any upper level by clicking on the border links.

To actually create these border links, we need to add anchor elements (<a href="...">) inside each of our comments, and style these anchor elements to look like the borders that can be clicked. Let’s add them to our first code example above.

Here are a few things to understand about the changes made:

  • Anchor links are added inside each of the comments, and these are styled to look like borders on the left-hand side.
  • The body of the comment is now aligned with the replies.
  • The .comment-heading (which contains the votes, author, and time added) has a fixed height of 50px. Therefore, by giving the border links the properties of position:absolute, top: 50px, and height: calc(100% - 50px), we are ensuring that they will start right below the heading, and go all the way down to the end of the comment. If you are not familiar with the calc() function, you can read this cool guide by Chris.
  • The border links have a clipped background, and are also given a width of 12px along with a border-width of 4px on the left and right. This means that while the visible area is only 4px wide, the actual clickable area is 12px wide. A wider surface is to help the users have an easier time actually positioning their pointers on the link and clicking it, because 4px is a little too narrow, but anything wider would look off visually.

With all that, we have knocked out the first four of the points mentioned above. Let’s add more comments to the code example to see how it would look.

Allowing users to hide/show comments with a click

At this point, we have a pretty darn satisfactory comment thread. This design by itself can work for quite a lot of real life use cases. However, let’s go one step farther and add our toggle feature, i.e. hiding and showing comments with a click.

The quickest and easiest way to allow users to hide and show comments with a click is to make use of the <details> and <summary> elements. To put it simply, the visibility of the entire <details> can be toggled by clicking on the <summary>. There is no JavaScript involved, and these tags are supported by ~96% of browsers at this moment. Once again, if you are unfamiliar with these concepts, you can learn more in yet another article from Chris.

Anyway, to actually implement this, we need to make the following changes to our code:

  • Change comments from <div> to <details>, i.e. all the elements with the class .comment will now be a <details> element.
  • Wrap the comment heading (.comment-heading) inside of a <summary> tag.
  • Provide a visual cue to the user to tell them whether a comment is hidden or not.

Seems easy enough. Anyway, here’s our new implementation:

Here are the final things to understand about the changes made:

  • The comments are now <details>, and they are all given the open attribute, so they are visible (i.e. open) by default.
  • The comment headings are now wrapped inside of <summary> tags. The default arrow is also removed.
  • The cue for the visibility status of the comments is mainly created using small text at the right of the comment. The content of this text changes depending on whether the parent <details> element has the open attribute or not. The text itself is a simple pseudo-element created using the ::after selector. Moreover, closed comments also have a border on the bottom to show the users that there is more to see.
  • At the very end of the CSS code, you will find some styles inside the @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {...} selector. This is a hack that only targets Internet Explorer. You see, IE does not support <details>, so they are always open by default there. By removing the text and resetting the cursor, IE users will see a regular comment thread, only without the ability to actually toggle the comment visibility. So, no harm, no foul.

Honorable mentions

Before we end this article, let’s talk about a few more things that is worth considering when designing comment threads in real-life applications.

What if the comment thread has no comments to show?

This may be a very simple problem to solve, but it is often easy to overlook these simple things. If a comment thread does not have any comments (empty state), we need to communicate that clearly to the user. A simple paragraph containing the line “There are no comments yet.” along with a form containing a text box and submit button can go a very long way, and should be the bare minimum when dealing with empty states. If you want to go the extra mile, you can also have a nice image (that communicates the message) accompanying the form.

How to handle the form for replying to a comment?

When it comes to the form for replying to a comment, different websites have different implementations. Some use the old fashioned way of redirecting users to a new page which contains the form — a simple text box with a submit button. Others open up a form right within the comment thread itself, usually with a simple toggle. The latter paradigm obviously requires JavaScript, but it is more more popular these days. For instance, in our example above, we could have a simple form which can be toggled by clicking on the Reply button, like so:

In the above example, we added simple forms inside the comment bodies, and gave them the class .d-none by default, which sets display: none; and hides them from view. Thanks to the simple event listener, any button with the attributes data-toggle="reply-form" and data-target="{{ comment_reply_form_id }} can be clicked to toggle the visibility of the forms. This is a very simple example of handling the reply forms with ease.

Where to place a new reply after a user is done posting it?

Let’s say a user replies to a comment using a form similar to the one shown above. Do you show it above the existing replies or below it? The answer is that it should always be shown above the other replies right after the user posts it for the first time. When a person fills out a form and submits it, they want immediate feedback to tell them that it worked. Therefore, by placing the new reply above the others, we are providing this feedback to the user without them needing to scroll down. On subsequent loads, you can of course arrange your comment replies according to whatever algorithm you see fit for your website.

Handling Markdown and code blocks

Many websites, particularly developer blogs, need to support markdown and code blocks in their comments. This is a much bigger discussion, perhaps warranting a dedicated article on this topic. However, for the sake of this article, let’s just say that there are plenty of Markdown editors out there that you can attach to a text box quite easily. Most of them work with JavaScript, so they should be fairly easy to integrate in our examples. One such plugin is markdown-it, which has a permissive MIT license. You can also look into WYSIWYG editors, which also serve a very similar purpose when it comes to comments on the web.

Spam prevention and user authentication

If you give users a form to provide their inputs, you can guarantee that you will find spam coming your way, so this is obviously an issue to address when building comment threads. A great way to reduce spam is to use services like reCAPTCHA from Google. For instance, in our example above, a reCAPTCHA box could be placed right below the Submit button in the reply forms. This would protect our website from abuse.

Another way to prevent spam is to only allow authenticated users to post comments, i.e. a user must have an account and be logged in to post a comment. Every comment would obviously be linked to an account, so this has the benefit of allowing moderators to handle users who continuously post spam or low effort content. In terms of handling it in the UI, a great way of doing it is by redirecting users to a login page when they click on the Reply or Post comment button if they are not logged in. Once they complete the authentication process, we can simply redirect them back to the comment thread and open up the form.


And we are done! We have fulfilled all five of our points, and have designed a nice-looking comment thread that is highly usable and accessible, with some cool features like jumping to comments, and togging the visibility of each comment. We also talked about forms inside comment threads, and discussed other things to consider in real-life applications. The bulk of our comment thread works using only CSS (no JavaScript), which goes to show you how far CSS has actually come.