Styling Comment Threads

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.

Adding links that jump to comments

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.


The post Styling Comment Threads appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Considerations for Making a CSS Framework

Around eight months ago, I started building a framework which would eventually go on to become Halfmoon. I made a post on this very website announcing the launch of the very first version. Halfmoon has been billed as a Bootstrap alternative with a built-in dark mode feature, that is especially good when it comes to building dashboards and tools. All of this still applies to the framework.

However, today I would like to talk about an area of the framework that is a bit understated. I believe our industry as a whole seriously underestimates the value of customization and user personalization, i.e. users being able to set their own design preferences. Chris has written before about knowing who a design system is made for, pointing out a spectrum of flexibility depending on who a system is meant to help.

But it’s more than design systems. Let’s talk about how Halfmoon addresses these issues because they’re important considerations for knowing which framework works best for your specific needs.

Dashboard built using Halfmoon

Who is Halfmoon for?

Before diving in, let’s address an important question: Is Halfmoon the right framework for you? Here’s a list of questions to help you answer that:

  • Are you building a dashboard, tool, or even a documentation website? Halfmoon has many unique components and features that are specific to these use cases.
  • Are you familiar with Bootstrap’s class names, but wish that the design was a bit more premium-looking?
  • Does your users want or expect a dark mode on your website?
  • Do you dislike dependencies? Halfmoon does not use jQuery, and also has no build process involving CSS preprocessors. Everything is pure, vanilla CSS and JavaScript.
  • Are you tired of dealing with complex build systems and front-end tooling? This ties in to the previous point. Personally, I find it difficult to deal with front-end tooling and build processes. As mentioned above, Halfmoon has no build process, so you just pull in the files (local, CDN, or npm), and start building.

If you answered yes to any (or all) of these questions, you should probably give Halfmoon a try. It is important to note however, that Halfmoon is not a UI component library for React/Vue/Angular, so you shouldn’t go into it expecting that. Moreover, if you are more fond of purely utility driven development, then Tailwind CSS is a better option for you. When it comes to CSS utilities, Halfmoon takes a middle of the road approach – there are utilities plus semantic classes for common components.

Using CSS custom properties

First, let’s get the easy stuff out of the way. CSS custom properties are incredible, and I expect them to completely replace preprocessor variables in the future. Browser support is already at a solid ~96%, and with Internet Explorer being phased out by Microsoft, they are expected to become a standard feature.

Halfmoon is built entirely using CSS variables because they provide a huge degree of customization. Now, you might immediately think that all this means is that there are a few custom properties for colors sprinkled in there, but it’s more than that. In fact, there are over 1,500 global variables in Halfmoon. Almost everything can be customized by overriding a property. Here’s a nifty example from the docs:

Halfmoon customization using CSS variables
Swapping out a few custom property values opens up a ton of possibilities in Halfmoon, whether it’s theming things for a brand, or tweaking the UI to get just the right look.

That’s what we’re talking about here when it comes to customization: does the system still stand up and work well if the person using it overrides anything. I have written extensively about this (and much more) in the official Halfmoon docs page.

Variables aren’t a new concept to frameworks. Many frameworks actually use Sass or Less variables and have done so for quite a while. That’s still a good and effective way to establish a customizable experience. But at the same time, those will lock into a preprocessor (which, again, doesn’t have to be a bad thing). By relying instead on CSS custom properties — and variable-izing all the things — we are relying on native CSS, and that doesn’t require any sort of build dependency. So, not only can custom properties make it easier to customize a framework, but they are much more flexible in terms of the tech stack being used.

There is a balance to be had. I know I suggested creating variables for everything, but it can be equally tough to manage and maintain scores and scores of variables (just like anything else in the codebase). So, lean heavily on variables to make a framework or design system more flexible, but also be mindful of how much flexibility you need to provide and whether adding another variable is part of that scope.

Deciding what components to include

When it comes to building a CSS framework, deciding what components to include is a big part of that ordeal. Of course, for a developer working on a passion project, you want to include everything. But that is simply not feasible, so a few decisions were made on my part.

As of right now, Halfmoon has most of the components you can find in similar frameworks such as Bootstrap or Bulma. These frameworks are great and widely used, so they are a good frame of reference. However, as I have mentioned already, a unique thing about Halfmoon is the focus on building tools and dashboards on the web. This niche, if you could call it that, has led me to build some unique components and features:

  • 5 different types of sidebars, with built-in toggle and overlay handlers. Sidebars are very important for most dashboards and tools (and a pain to get right), so this was a no brainer.
  • 2 different types of navbars. There is one that sticks to the bottom of the page, which can be used to great effect for action buttons. Think about the actions that pop up when you select items on data-table. You could place those action buttons here.
  • Omni-directional dropdowns (with 12 different placements, 3 for each direction).
  • Beautiful form components.
  • Built-in keyboard shortcut system, with an easy way to declare new ones for your tool.
  • Tons of utilities. Of course, this is not comparable to Tailwind CSS, but Halfmoon has enough responsive utility classes to handle a lot of use cases right out of the box.

Moreover, the built-in dark mode, huge customizability, and the standard look and feel to the components, should all work together to make Halfmoon a great tool for building web tools and dashboards. And I am hopefully nowhere close to being done! The next updates will bring in a form validator (demo video), more form components, multi-select component, date and time picker, data-table component, etc.

So what is exactly missing from Halfmoon? Well the most obvious ones are tabs, list group, and spinners. But all of these are planned to be added in v1.2.0, which is the next update. There are also other missing components such as carousels, tree navigation, avatars, etc, which are slightly out of scope.

Providing user preferences

Giving end users the ability to set their preferences is often overlooked by frameworks. Things like setting the font size of an article, or whether to use a dark or light theme. In some ways, it’s sort of funny, because the web is catching up to what operating systems have allowed users to do for decades.

Here are some examples of user personalization on the web:

  1. Being able to select your preferred color mode. And, even better, the website automatically saves and respects your preference when the page is loaded. Or better yet, looking at your operating system preferences and automatically accommodating them.
  2. Setting the default size of elements. Especially font size. A small font might look good in a design, but allowing users to set their ideal font size makes the content actually readable. Technically, every modern browser has an option to zoom into content, but that is often unwieldy, and does not actually save your settings.
  3. Setting the compactness of elements. For example, some people prefer large padding with rounded corners, while others find it a waste of space, instead preferring a tighter UI. Sort of like how Gmail lets you decide whether you want a lot of breathing room in your inbox or make it as small and tight as possible to see more content.
  4. Setting the primary color on the website. While this is entirely cosmetic, it is still charming to be able to set your favorite color on every button and link on a website.
  5. Enabling a high contrast mode. Someone pointed this out to me on GitHub. Apparently, many (and I mean many) CSS frameworks often fail the minimum contrast recommended between foreground and background colors on common elements, such as buttons. That list includes Halfmoon. This is often a tradeoff, because overly contrastive elements often look worse (purely in terms of aesthetic). User personalization can allow you to turn on a high contrast mode, if you have difficulty with the default contrast.

Allowing for user personalizations can be really difficult to pull off — especially for a framework — because that would could mean swapping out huge parts of CSS to accommodate the different personalization settings and combinations. However, with a framework like Halfmoon (i.e. built entirely using CSS variables), this becomes trivial as CSS variables can be set and changed on run-time using JavaScript, like so:

// Get the <html> tag (for reading and setting variables in global scope)
var myElement = document.documentElement;

// Read CSS variable
getComputedStyle(myElement).getPropertyValue("--variable-name");

// Set CSS variable
myElement.style.setProperty("--variable-name", "value");

Therefore, user personalization can be implemented using Halfmoon in the following way:

  • The user sets a preference. That basically means a variable value gets changed. The variable is set with JavaScript (as shown above), and the new value is stored in a cookie or local storage.
  • When the user comes back to the website, their preferences are retrieved and set using JavaScript (again, as shown above) once the page is loaded.

Here are visual examples to really hammer the point home.

Setting and saving the default font size

In the example above, whenever the range slider is changed, the variable --base-font-size is updated to the slider’s value. This is great for people who prefer larger text. As explained in the previous section, this new value can be saved in a cookie or local storage, and the next time the user visits the website, the user preference can be set on page load.

Setting the compactness of content

Compact theme using CSS variables
Because there are CSS custom properties used as utilities, like spacing and borders, we can remove or override them easily to create a more compact or expanded component layout.

Only two variables are updated in this example to go from an expanded view to a compact one:

  • --content-and-card-spacing changed from 3rem (30px) to 2rem (20px).
  • --card-border-radius changed from 0.4rem (4px) to 0.2rem (2px).

For a real life scenario, you could have a dropdown that asks the user whether they prefer their content to be Default or Compact, and choosing one would obviously set the above CSS variables to theme the site. Once again, this could be saved and set on page load when the user visits the website on their next session.

Wait, but why?

Even with all the examples I have shown so far, you may still be asking why is this actually necessary. The answer is really simple: one size does not fit all. In my estimate, around half of the population prefers a dark UI, while the other half prefers light. Similarly, people have wild variations about the things they like when it comes to design. User personalization is a form of improving the UX, because it lets the user choose what they prefer. This may not be so important on a landing page, but when it comes to a tool or dashboard (that one has to use for a long time to get something done), having a UI that can be personalized is a boon to productivity. And knowing that is what Halfmoon is designed to do makes it ideal for these types of use cases.

Moreover, you know how people often complain that websites made with a certain framework (eg Bootstrap) all look the same? This is a step toward making sure that websites built with Halfmoon will always look distinct, so that the focus is on the website and content itself, and not on the framework that was used to build it.

Again, I am not saying that everything should be allowed to be personalized. But knowing who the framework is for and what it is designed to do helps make it clear what should be personalized.

Looking ahead

I strongly feel that flexibility for customization and accounting for user preferences are often overlooked on the web, especially in the framework landscape. That’s what I’m trying to address with Halfmoon.

In the future, I want to make it a lot easier for developers to implement user preferences, and also promote diversity of design with new templates and themes. That said, here are some things on the horizon for Halfmoon:

  • A form validator (demo video)
  • New components, including range sliders, tabs and spinners
  • High contrast mode user preference
  • Multi-select component (like Select2, only without jQuery)
  • A date and time picker
  • A data-table component
  • A GUI-based form builder
  • More themes and templates

You can, of course, learn more about Halfmoon in the documentation website, and if you want to follow the project, you can give it a star on GitHub.


The post Considerations for Making a CSS Framework appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Halfmoon: A Bootstrap Alternative with Dark Mode Built In

I recently launched the first production version of Halfmoon, a front-end framework that I have been building for the last few months. This is a short introductory post about what the framework is, and why I decided to build it.

The elevator pitch

Halfmoon is a front-end framework with a few interesting things going for it:

  • Dark mode built right in: Creating a dark mode version of a site is baked in and a snap.
  • Modular components: A lot of consideration has gone into making modular components — such as forms, navbars, sidebars, dropdowns, toasts, shortcuts, etc. — that can be used anywhere to make layouts, even complex ones like dashboards.
  • JavaScript is optional: Many of the components found in Halfmoon are built to work without JavaScript. However, the framework still comes with a powerful JavaScript library with no extra dependencies.
  • All the CSS classes you need: The class names should be instantly familiar to anyone who has used Bootstrap because that was the inspiration.
  • Cross-browser compatibility: Halfmoon fully supports nearly every browser under the sun, including really old ones like Internet Explorer 11.
  • Easily customizable: Halfmoon uses custom CSS properties for things like colors and layouts, making it extremely easy to customize things to your liking, even without a CSS preprocessor.

In many ways, you can think of Halfmoon as Bootstrap with an integrated dark mode implementation. It uses a lot of Bootstrap’s components with slightly altered markup in many cases.

OK, great, but why this framework?

Whenever a new framework is introduced, the same question is inevitably pops up: Why did you actually build this? The answer is that I freaking love dark modes and themes. Tools that come with both a light and a dark mode (along with a toggle switch) are my favorite because I feel that being able to change a theme on a whim makes me less likely to get bored looking at it for hours. I sometimes read in dim lighting conditions (pray for my eyes), and dark modes are significantly more comfortable in that type of situation. 

Anyway, a few months ago, I wanted to build a simple tool for myself that makes dark mode implementation easy for a dashboard project I was working on. After doing some research, I concluded that I had only two viable options: either pickup a JavaScript-based component library for a front-end framework — like Vuetify for Vue — or shell out some cash for a premium dark theme for Bootstrap (and I did not like the look of the free ones). I did not want to use a component library because I like building simple server-rendered websites using Django. That’s just my cup of tea. Therefore, I built what I needed: a free, good-looking front-end framework that’s along the same lines as Bootstrap, but includes equally good-looking light and dark themes out of the box.

Future plans

I just wanted to share Halfmoon with you to let you know that it exists and is freely available if you happen to be looking for an extensible framework in the same vein as Bootstrap that prioritizes dark mode in the implementation.

And, as you might imagine, I’m still working on Halfmoon. In fact I have plenty of enhancements in mind:

  • More components
  • More customization options (using CSS variables)
  • More examples and templates
  • Better tooling
  • Improved accessibility examples in the docs
  • Vanilla JavaScript implementations of useful components, such as custom multi-select (think Select2, only without jQuery), data tables and form validators, among other things.

In short, the plan is to build a framework that is really useful when it comes to building complex dashboards, but is still great for building any website. The documentation for the framework can be found on the project’s website. The code is all open-source and licensed under MIT. You can also follow the project on GitHub. I’d love for you to check it out, leave feedback, open issues, or even contribute to it.


The post Halfmoon: A Bootstrap Alternative with Dark Mode Built In appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.