A static word cloud with HTML and CSS


8 - 10 minutes to read, 2020 words
Categories: web
Keywords: css html web

The keywords and categories pages help to provide an overview and organize, all content I’m writing down and making public.

For example, notes about C++ are then grouped in an appropriate page, and all links are, for usability, sorted more or less alphabetically.

Visually, it’s just a list of links, which is definitively practical but only takes advantage of the horizontal space, even on smaller devices Another disadvantage is that it is not possible to know how many notes are hidden behind a link.

I decided to make a so-called tag cloud to overcome both issues.

A Tag cloud is a set of words with weighted sizes.

Prepare the data

Before the change, the data looked like

<nav>
    <ul>
        <li><a href="/keywords/%23pragma/">#pragma</a></li>
        <li><a href="/keywords/%3Ddelete/">=delete</a></li>
        <li><a href="/keywords/API/">API</a></li>
        <li><a href="/keywords/GRUB/">GRUB</a></li>
        <li><a href="/keywords/IFS/">IFS</a></li>
        <li><a href="/keywords/JSON/">JSON</a></li>
        <li><a href="/keywords/POSIX/">POSIX</a></li>
        <li><a href="/keywords/UEFI/">UEFI</a></li>
        <li><a href="/keywords/VirtualBox/">VirtualBox</a></li>
        <li><a href="/keywords/WSL/">WSL</a></li>
        <li><a href="/keywords/abseil/">abseil</a></li>
        <li><a href="/keywords/add_custom_command/">add_custom_command</a></li>
        <li><a href="/keywords/add_custom_target/">add_custom_target</a></li>
        <li><a href="/keywords/admin/">admin</a></li>
        <li><a href="/keywords/administrator/">administrator</a></li>

Since HTML5 it is possible to define custom data attributes, and those can be accessed in CSS.

So I changed the HTML to include the weight, which is related to the number of associated notes. I also added a class="cloud" to make it easier to define the styling rules.

<nav class=cloud>
    <ul>
        <li><a href="/keywords/%23pragma/" data-weight="1">#pragma</a></li>
        <li><a href="/keywords/%3Ddelete/" data-weight="1">=delete</a></li>
        <li><a href="/keywords/API/" data-weight="1">API</a></li>
        <li><a href="/keywords/GRUB/" data-weight="1">GRUB</a></li>
        <li><a href="/keywords/IFS/" data-weight="1">IFS</a></li>
        <li><a href="/keywords/JSON/" data-weight="3">JSON</a></li>
        <li><a href="/keywords/POSIX/" data-weight="2">POSIX</a></li>
        <li><a href="/keywords/UEFI/" data-weight="1">UEFI</a></li>
        <li><a href="/keywords/VirtualBox/" data-weight="1">VirtualBox</a></li>
        <li><a href="/keywords/WSL/" data-weight="3">WSL</a></li>

This change alone does not affect the visual appearance of the website. So far so good.

Create appropriate CSS rules for making a cloud

Even if talking about "clouds", the described tag cloud looks more similar to a "tag wall". I’m sure it is possible to define a nice cloud in CSS, but I kinda like the "tag wall".

nav.cloud ul {
    /* Remove the bullet points of the list */
    list-style: none;


    /* make sure that all elements will be visible by wrapping into multiple lines */
    display: flex;
    flex-wrap: wrap;

    /* center the content */
    padding-left: 0;
    align-items: center;
    justify-content: center;
}

nav.cloud a {
    /* add some padding between elements, otherwise no space between words */
    padding-left: .25rem;
    padding-right: .25rem;
}

With those changes, the list does not look like a list anymore, but like a wall of text.

While it takes advantage of all the horizontal space, it makes the page harder to use because all pieces of information are too dense.

Change the font size

To make the list more readable, and provide some information about how many notes are behind the link in the list, let’s set the (relative) font size depending on the attribute data-weight:

nav.cloud a[data-weight= "1"] { font-size: 0.9rem; }
nav.cloud a[data-weight= "2"] { font-size: 1.0rem; }
nav.cloud a[data-weight= "3"] { font-size: 1.2rem; }
nav.cloud a[data-weight= "4"] { font-size: 1.4rem; }
nav.cloud a[data-weight= "5"] { font-size: 1.6rem; }
nav.cloud a[data-weight= "6"] { font-size: 1.8rem; }
nav.cloud a[data-weight= "7"] { font-size: 2.0rem; }
nav.cloud a[data-weight= "8"] { font-size: 2.2rem; }
nav.cloud a[data-weight= "9"] { font-size: 2.4rem; }
nav.cloud a /* >= 10 */       { font-size: 2.6rem; }

This makes the list much nicer to use and already looks like a tag cloud.

It might be a good idea to try to make the spaces between the lines more uniform, thus adding something like line-height: 3rem; to nav.cloud, otherwise lines with tags with smaller fonts are too close together.

A colored cloud

Some tags are composed of multiple words, like link:/keywords/build system/[build system]. Because links are underlined, it is possible to recognize easily what composes a tag.

But I wanted to remove the underline because…​ all words are underlined, and will always hyperlink. The underline (and blue color) are for making it visually clear that there is a link and not simply text. In this context, this styling is not relevant, on the contrary, it distracts.

The underline can be easily be removed with

nav.cloud a {
    text-decoration: none;
}

Unfortunately, this makes it hard to recognize when multiple words are single or multiple tags.

This can be remedied by adding colors. It would be possible to alternate between two or more colors, I think the tag clouds look better if the sequence of colors looks random/is not easily recognizable

nav.cloud li:nth-child(1n+1) a {color:var(--red)}
nav.cloud li:nth-child(2n+1) a {color:var(--yellow)}
nav.cloud li:nth-child(3n+1) a {color:var(--blue)}
nav.cloud li:nth-child(5n+1) a {color:var(--green)}
nav.cloud li:nth-child(7n+1) a {color:var(--orange)}
nav.cloud li:nth-child(7n+1) a {color:var(--violet)}

with --red, --yellow, …​ defined somewhere else in your CSS file, or just write the hex values directly.

What about accessibility?

For me, the tag cloud as presented is more accessible than a simple list.

Horizontal space is also used, which means on smaller displays there is less need to scroll up and down.

The fact that words are sized differently also an additional piece of information. For me, there mustn’t be too much difference between the biggest and smallest font.

Otherwise, it is also good if there are no synonyms or words that differ only in uppercase/lowercase and singular/plural. But this also holds for a simple unstyled list.

On a textual browser, like w3m or Lynx, or other browsers that do not support or ignore CSS, the webpage looks unchanged.

Words are also not rotated (why should that be a good idea?)

Should I use an unordered list <ul> or ordered <ol>

My words are ordered more or less alphabetically. This is because I believe it is the sanest way so sort words. I’m writing more or less because when taking into account diagrams of different languages, case sensitivity, and natural sorting, there are multiple ways to sort things in an useful way, depending on your expectations.

One could claim that the most popular words or words linked to more notes should go first, but it means that the relative position of elements changes over time.

Sorting by other criteria (length of an URL, most clicked link…​) does not provide anything to the end-user (me).

So why am I using <ul> and not <ol>? Most (non-official) resources I could find claim that <ol> is the way to go to be accessible?

The first reason is that, semantically, it is not important if the list is sorted or not.

It’s not a list that defines the links which you need to click on, in this case, the ordering might be relevant. It’s a list of words, and for convenience is sorted alphabetically.

If some programs (no idea if there is one) show an unsorted list in a different order, that’s a feature. The user wants the list sorted in the other criteria which he prefers, why should I, with no good reason, impose a sorted list?

The other reason is that because it is not a list of links you have to sort in a specific folder, I do want bullet points and not numbers attached to every element of the list.

Of course, with CSS it is possible to change the default behavior, but in a browser that ignores CSS, like textual browsers, this is not possible. As I use textual browsers too, I am interested in having bullet points there too.

I also noticed that https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Navigation_Role uses <ul> as an example too, and has notes about accessibility, and none mentions <ol>.

Alternate approaches

Do not use a list at all

Instead of styling a list so that it does not look line one, it could be possible to write all the links one after the other.

It makes sense, but maybe not so much when there is not styling, as the "wall of links" is be less readable, otherwise I would not have added colors and different font sizes.

More dynamic font-sizing

It should be possible to write something like

nav.cloud a[data-weight="1"] { --weight: 1; }
nav.cloud a[data-weight="2"] { --weight: 2; }
nav.cloud a[data-weight="3"] { --weight: 3; }
nav.cloud a[data-weight="4"] { --weight: 4; }
nav.cloud a[data-weight="5"] { --weight: 5; }
nav.cloud a[data-weight="6"] { --weight: 6; }
nav.cloud a[data-weight="7"] { --weight: 7; }
nav.cloud a[data-weight="8"] { --weight: 8; }
nav.cloud a[data-weight="9"] { --weight: 9; }
nav.cloud a /* >= 10 */      { --weight: 10; }

nav.cloud a {
  font-size: calc( /* desired formula with var(--weight) */);
}

The reason I did not implement it that way is because finding out the desired formula is not as useful, it make it a lot harder to adapt a single value. Another contributing factor is this bug in cssmin. Unfortunately this program seems to be unmantained, there is no official successor, and I could find no alternate program in most repositories. Granted, I could avoid minifying the CSS files, but it’s one of the many points that makes web development sad

Another, even more terse approach, would be to write

ul.cloud a {
  --weight: attr(data-weight number, 2);
  font-size: calc( /* desired formula with var(--weight) */);
}

but for the already given reasons, I decided the presented approach.


Do you want to share your opinion? Or is there an error, some parts that are not clear enough?

You can contact me here.