How the tags search site works on a static webpage like this using nothing but css (and a downloaded search-index).

Why???

You may have noticed that this website is created using jekyll, meaning it is purely static content. On top of that it doesn’t even use java-script, partially because I like the challenge of making things work without it, partially because I just hate writing javascript. This complicates using tags quite a bit however, since the whole point of them is making it easier to find things more quickly by filtering. As I don’t want to start compiling rust or something similar to webassembly for this, I had to make due with good old html and css.

How is this even possible

There are two main ticks at play here.

The first beeing the sibling selector ~ combined with the :checked (or really any) pseudo-class. For example a rule like

input:checked ~ * {
	color:green;
}

will turn anything following the checkbox green, when the checkbox is checked. In my case I set display: none to hide things when the checkbox is checked.

The second one is the implicit any-matching used by css rules targeting classes. In simpler terms, a css rule targeting a class matches an object if any of it’s classes is the targeted one. To make use of this a bit of logic is required:

Since we want to show only articles that include all the tags we selected, we can introduce a double negation and not show any articles that don’t have a tag we selected. The latter is now implementable using css as follows:

  • assign each article all the tags it does not have as css classes
  • for each tag, have a checkbox and a css-rule that hides all articles not having that tag when checked
  • display all articles by default.

Pitfalls

  • having the checkboxes in a separate subtree

    This one should be self-explanatory, the checkboxes need to be siblings of some ancestor you want to apply rules to. This can be horrible for layouting, but you can always hide them and use labels for them (just remember that labels don’t have a :checked pseudo-class).

  • css selectors not supporting brackets

    if you want to use multiple pseudo-classes, you have to copy-paste ~ * or whatever you want to apply styling to aswell.

My Nice-To-Have addons

  • chosen tags change color

    This is fairly easy to implement, just a rule similar to the hiding but instead targeting the checkbox or label itself:

    tag:checked + label {
    	background: black;
    }
    
  • links to the page with a tag preselected

    In addition to the :checked pseudo-class I also use the :target pseudo-class, which is assigned to any element whose ID is chose via URL. The downside there is that you can’t un-check it because that doesn’t remove the :target pseudo-class. This could probably be fixed by having a bunch of rules applying the inverse operations if :target, but that seems like a lot of rules for a very minimal improvement.

  • clear selection

    This just reloads the page (additionally without any target, so even the stuck selections are cleared)

Putting it all together

Of course you can just head over to tags and inspect the webpage, but here are the crucial snippets:

The HTML looks something like this (of course there are more css classes for styling it nicely)

<div>
	<a href="/tags">clear selection</a>

	<input type="checkbox" id="linux"/>
	<label for="linux">Linux</label>
	<!-- More of these label + checkbox things -->

	<div class="not_linux not_css">
		<!-- a result with all available but the linux and css -->
	</div>
	<!-- more results wrapped in such a div -->
</div>

And the css rules (relevant for the functionality, not for the styling):

/* hide the checkbox */
input {
	display:none;
}

/* color the label following a selected checkbox */
input:checked + label, input:target + label {
	background: black;
}

/* Hide any result not having linux a tag following a selected linux tag */
#linux:checked ~ .not_linux,#linux:target ~ .not_linux,#linux:hover ~ .not_linux {
	display:none;
}
/* you need one of these for every tag */