Often privacy focused users will browse the web without JavaScript. At Packet Point we want to support these users to the fullest extent possible.

Instead of using JavaScript we use CSS to emulate same the interactive functionality.

We will discuss the techniques used thought out Packet Point to retain full functionality for all users without JavaScript. In this post, we will focus on the server customization page.

Desired Functionality

During component selection, we want to display a summary of the options selected and the total cost at the side of the page.

Without Clever CSS

One possible implementation would be to fully utilize server-side rendering, each option could be a button that when clicked could trigger a form request reloading the entire page, which would allow the server to fill in the summary and total cost server-side.

The most severe downside of this approach is the increased latency. Every time the user changes an option a full page reload is required. This latency issue is exacerbated by the fact that privacy focused users typically use a VPN or Tor which can increase latency 100-fold.

Most often sites just disable this functionally for users without JavaScript or do not support them at all, which is the other option.

Client Side Rendering With CSS

Conditional Styling with :checked

To be interactive we will need to be able to detect user input.

Using CSS pseudo-class selectors we can trigger CSS on a number of different inputs. Some selectors include

  • :hover which selects elements on hover
  • :focus which selects elements that are focused
  • :checked which selects input elements which are checked

Later examples will show :checked in action.

Spooky Action at a Distance

To do something useful with :checked we are going to need to be able to modify the style of another element conditionally on the :checked state. Further, we are going to want a nice looking interface that's more than just a bunch of checkboxes.

Controlling the Checkbox At a Distance

Using the for attribute on a label tag we can create an element that once clicked toggles the checkbox from anywhere on the page. The for attribute takes the ID of an input tag.

<input type="checkbox" id="box"/>
<label for="box">Hello</label>

We can now style the label freely to create whatever interface we want. Further, the checkbox can be made display:none; to completely hide them away.

Styling Arbitrary Elements Conditionally

Commonly, the adjacent sibling selector (+) is used in conjunction with :checked to style the label directly preceding the input element.

input[type="checkbox"]:checked + label {
    font-size: 3rem;
}

This works great and works generically without having to specify the specific input/label pair.

However, we are going to need to effect more elements than just the next one. Thankfully, we have more CSS combinators to work with:

  • '+' Adjacent Sibling Selector
  • '~' General Sibling Selector
  • '>' Child Selector
  • ' ' Descendant Selector

For a deeper examination of these combinators refer to the Mozilla developer documentation.

Below you can see an example of using these combinators together to select different elements conditionally on the state of a checkbox. By clicking on different selectors you can see which elements would be selected.

<body>
<nav>
<h1>Title</h1>
</nav>
<main>
<h2>Example</h2>
<input type="checkbox" id="box" checked/>
<div class="report">
<span class="a"><div></div></span>
<span class="b"><div></div></span>
<div></div>
</div>
<div class="report">
<span class="a"><div></div></span>
<span class="b"><div></div></span>
<label for="box">Hi</label>
</div>
<span>date</span>
</main>
<footer></footer>
</body>

An important limitation of these selectors is we won't be able to conditionally style sibling elements that come before the input tag nor elements which are not a descendant of the parent of the input tag. In the above example, this means we can't conditionally style the h2 tag which comes before the input tag nor the footer or main tag which are not descendants of the input tag's parent main.

However, we can work around this limitation by nesting every element which needs conditional styling inside a sibling element that comes after the input tag.

<body>
    <input type="checkbox" id="box1"/>
    <input type="checkbox" id="box2"/>
    <main>
    ...
    </main>
</body>

Using the above construction, every element inside the main tag will be conditionally style-able by the input boxes.

Say you have an element with the class name 'a', then we can conditionally style it with the following CSS.

    #box1:checked ~ main .a { ... }      /* when checked */
    #box1:not(:checked) ~ main .a { ... }/* when not checked */

A lot can be accomplished using only the techniques we have shown so far, however let's look at couple more CSS tricks that will complement these techniques.

:checked Works on Radio Button as Well

Radio buttons work similarly to checkboxes except clicking an already checked radio buttons will remain checked and if there are multiple radio buttons with the same name attribute in the same form then only one of them can be checked at a time.

For server customization, most components have mutually exclusive options. So radio buttons are used to represent the state of the options selected.

Selecting on the State of Multiple Checkboxes

Suppose we have the following HTML

<input type="checkbox" id="box1"/>
<input type="checkbox" id="box2"/>
<main><span class='output'></span></main>

The following CSS can be used to control the text inside .output for each of the possible combinations of inputs.

#box1:not(:checked) ~ #box2:not(:checked) ~ main .output:before{content:"neither checked"}
#box1:checked       ~ #box2:not(:checked) ~ main .output:before{content:"1 checked but not 2"}
#box1:not(checked)  ~ #box2:checked       ~ main .output:before{content:"2 checked but not 1"}
#box1:checked       ~ #box2:checked       ~ main .output:before{content:"both checked"}

CSS Math with Counters

The server customization page has many components each with many options. By pre-computing the total cost of every possible combination and using the previous trick we could implement the price total display. The problem is 'every possible combination' is a lot, say we have 6 components each with 10 options each, then there would be 10^6 = 1,000,000 options to pre-compute, that would be a massive CSS file. Instead, we use CSS counters.

If you are unfamiliar with CSS counters or just need a refresher please consult the Mozilla developer documentation.

For using CSS counters here there are a couple of things to keep in mind. Firstly, elements that are 'display:none;' do not compute their counter-increment so we can't apply the counter directly to the input tag. Secondly, an element can only have one counter-increment value, so we are going to need multiple elements one for each counter-increment. We work around these issues by adding a 'b' tag after each input which can hold the counter.

So with the following HTML.

<input type="checkbox" id="box1"/>
<b></b>
<input type="checkbox" id="box2"/>
<b></b>
<span class='total'></span>

We would use the following CSS.

body { counter-reset: sum; }
#box1:checked + b { counter-increment: sum 1; }
#box2:checked + b { counter-increment: sum 7; }
.total:before { content:counter(sum); }

Putting it all together