Create a tag cloud with HTML and CSS

A tag cloud is a list of links associated with a term or a tag. It is common to see them in blogs and websites to highlight popular topics visually: more popular words/categories will have larger font sizes, and less popular topics will be presented in smaller font sizes:

Word cloud from JFK’s inauguration speech. Generated by

Basic HTML structure

As we mentioned before, a tag cloud is a list of links. So, from a semantic perspective, it makes sense to use an unordered list (<ul>) as the terms will not be sorted by popularity... otherwise, it would be a rather boring cloud.

<li><a href="/tag/word1">Word1</a></li>
<li><a href="/tag/word2">Word2</a></li>
<li><a href="/tag/word3">Word3</a></li>
<!-- ... -->
<li><a href="/tag/word1" data-weight="3">Word1</a></li>
<li><a href="/tag/word2" data-weight="7">Word2</a></li>
<li><a href="/tag/word3" data-weight="4">Word3</a></li>
<!-- ... -->
  • role: this is a navigational component; we want to identify it to screen readers and other assistive technologies (AT).
  • aria-label: used to give it a "title" or description to ATs.
<ul class="cloud" role="navigation" aria-label="Webdev tag cloud">
<li><a data-weight="4" href="/tag/http">HTTP</a></li>
<li><a data-weight="2" href="/tag/ember">Ember</a></li>
<li><a data-weight="5" href="/tag/sass">Sass</a></li>
<li><a data-weight="8" href="/tag/html">HTML</a></li>
<li><a data-weight="6" href="/tag/flexbox">FlexBox</a></li>
<li><a data-weight="4" href="/tag/api">API</a></li>
<li><a data-weight="5" href="/tag/vuejs">VueJS</a></li>
<li><a data-weight="6" href="/tag/grid">Grid</a></li>
<li><a data-weight="3" href="/tag/rest">Rest</a></li>
<li><a data-weight="9" href="/tag/javascript">JavaScript</a></li>
<li><a data-weight="3" href="/tag/animation">Animation</a></li>
<li><a data-weight="7" href="/tag/react">React</a></li>
<li><a data-weight="8" href="/tag/css">CSS</a></li>
<li><a data-weight="1" href="/tag/cache">Cache</a></li>
<li><a data-weight="3" href="/tag/less">Less</a></li>
Not much of a cloud

Styling the tag cloud

These are the features that we are going to add with CSS:

  • Make tags’ font-size depend directly on the value of data-weight.
  • Add the tag’s weight next to the label.
  • “Randomize” tags’ colors to provide.
  • Animate the :hover and :focus status, providing an accessible alternative to avoid motion.

Styling the <ul>

First, we delete the circles in the list items and remove the list indentation with list-style and a padding-left of zero:

list-style: none; 
padding-left: 0;
display: flex; 
flex-wrap: wrap;
align-items: center;
justify-content: center; {
list-style: none;
padding-left: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
line-height: 2.5rem;
Not great, but the cloud is forming little by little

Adjust tag size depending on the weight/popularity

Let’s start by styling the links a little: a {
color: #a33;
display: block;
font-size: 1.5rem;
padding: 0.125rem 0.25rem;
text-decoration: none;
position: relative;
The cloud starts looking like a cloud
attr([attribute-name] [attribute-unit]? [, default-value]?) a {
--size: attr(data-weight number, 2);
font-size: calc(var(--size) * 1rem);
} a[data-weight="1"] { --size: 1; } a[data-weight="2"] { --size: 2; } a[data-weight="3"] { --size: 3; } a[data-weight="4"] { --size: 4; } a[data-weight="5"] { --size: 5; } a[data-weight="6"] { --size: 6; } a[data-weight="7"] { --size: 7; } a[data-weight="8"] { --size: 8; } a[data-weight="9"] { --size: 9; } a {
--size: 4;
font-size: calc(var(--size) * 0.25rem + 0.5rem);
/* ... */
Not bad for a combined 50 lines of HTML and CSS

Add the tag’s popularity next to the label

Many tag clouds display not only the term but also the weight/value next to it.[data-show-value] a::after {
content: " (" attr(data-weight) ")";
font-size: 1rem;
  • Remove/Don’t add data-show-value: the numbers are not displayed.
Not a big fan of this visualization

Add colors

A monochrome cloud looks dull. We are going to spice it up by adding colors. And we are going to try two different approaches. li:nth-child(2n+1) a { --color: #181; } li:nth-child(3n+1) a { --color: #33a; } li:nth-child(4n+1) a { --color: #c38; }
Make sure the colors have enough contrast with the background a {
opacity: calc((15 - (9 - var(--size))) / 15);
/* ... */
This option may be more elegant… but less accessible

Customize outline

It is important to be extremely careful when touching the outline. It is a key property for accessibility, and a bad value could make the component (or the whole website) difficult to use for people with disabilities. a:focus {
outline: 1px dashed;

Add animations

To add some interactivity, we will add a simple animation: when the user focuses on or hovers over one of the links, the background will change color horizontally. a::before {
content: "";
position: absolute;
top: 0;
left: 50%;
width: 0;
height: 100%;
background: var(--color);
transform: translate(-50%, 0);
opacity: 0.15;
transition: width 0.25s;
} a:focus::before, a:hover::before {
width: 100%;
@media (prefers-reduced-motion) { * {
transition: none !important;


There are many ways to create a tag cloud: with HTML and CSS, with lists, without lists, using JavaScript… All (or most) of them valid and beautiful.

Full-Stack Software Engineer, Mobile Developer, Web technologies enthusiast. CSS aficionado. Twitter: @alvaro_montoro