About me

Hello! I am Leaf, a software developer with a passion for touching grass.

When I was 12 years old I wanted to make my own Minecraft mod, so I started learning Java, and I've been developing software ever since. I have experience with many different programming languages, and am even writing my own very weird and unoptimized language.

When my head's too full and I need to get away from my desk for a while, I like to go cycling through nature, often stopping to just sit down and relax. Despite my love for nature, I actually know surprisingly little about it, and I want to learn more about different kinds of plants and trees at some point.

Another one of my interests is playing sandbox games. I've been playing Minecraft since 2012, and have recently started playing Trailmakers, a game mostly focused around vehicle building.

An even older passion of mine is music. I've been playing different keyboard instruments like pipe organ and piano since I was 7 years old, and love to compose my own songs. Maybe I'll even upload some of them publicly, if I ever make any I think are good enough for that.

Current Projects

Psithurism

A programming language focused around the idea that everything is a function that can take and return any number of arguments.

Spacetraders SDK

The SDK I'm using to play SpaceTraders , an API-based space exploration game.

This Website

The website you're on right now was written using only HTML and CSS. You can read more about how I did that .

Here you'll find me documenting all the stupid stuff I do. If I remember to write posts that is.

You probably don't need Javascript.

Web developers are great at using HTML and CSS to make websites look beautiful, and they have developed many clever techniques to turn designs into reality. However, they wanted more. They wanted their websites to react to user interaction, so they started using Javascript in their designs.

Javascript is everywhere these days, not only on big fancy web applications, but also on static websites.
Want to add a light/dark theme switch? Javascript.
Want to make it so the user doesn't have to see your site reload every time they go to another page? Javascript.
Want to create some fancy popups to notify the user of the newest changes to your website? Javascript.

Is this a bad thing? No. Most people have Javascript enabled in their browsers, and those who don't know what they're getting into.
Despite that, I wondered how far you could get without any Javascript. And as it turns out, you can go far. Really far.

Origin

The idea for this website started when I remembered a little trick to make custom checkboxes I saw on W3Schools years ago. I realized that if you just made the entire page the "checkbox", you could make a light/dark theme switch without using any Javascript. A label element would serve as the actual switch to change the checkbox's state.

Now that this website is done, I want to share the techniques I used to make it, together with some other things I realized would be possible.

Table of contents

Theme switch

Let's start with a simplified version of this website's body:

<body>
  <header>
    <!-- navbar -->
  </header>
  <main>
    <!-- website content -->
  </main>
</body>

If we want to add the ability to switch between two themes to our website, we don't actually need to change anything about the structure of the website itself. We just need to add a checkbox at the very top of the body:

<body>
  <input id="themeswitch" type="checkbox">
  <header>

And some CSS:

/* This hides the checkbox, we'll add something better to actually click on later */
#themeswitch {
  display: none;
}

/* This will apply to both themes */
#themeswitch ~ * {
  color: var(--foreground);
  background-color: var(--background);
}

/* This will only apply to the initial theme, in this case the dark theme */
#themeswitch:not(:checked) ~ * {
  --background: #3f3f3f;
  --foreground: #ffffff;
}

/* This will only apply to the alternate theme, in this case the light theme */
#themeswitch:checked ~ * {
  --background: #efefef;
  --foreground: #5f5f5f;
}

But how does this all work? We can use :checked to match the checkbox only when it has been checked, then use :not(:checked) to match the checkbox only when it has not been checked.

To apply styles to the content of the website instead of the checkbox, we use ~. This is the "subsequent-sibling combinator", which means it selects all elements after the element that was matched by what is to the left of it, so in our website, that would be the header and main elements.

The only thing we need now is something to actually switch the themes. Let's add a fancy switch to the header that changes between two icons based on the active theme:

<header>
  <!-- navbar -->

  <label for="themeswitch" class="stack">
    <img class="dark" src="icons/sun.png">
    <img class="light" src="icons/moon.png">
  </label>
</header>

And the CSS:

.stack {
  display: grid;
}
.stack > * {
  /* This forces all elements that are in the stack on top of each other */
  grid-area: 1 / 1 / 2 / 2;
  /* This prevents the stack from getting too big */
  min-width: 0;
  min-height: 0;
}

/* This hides .light elements when in dark mode, and .dark elements when in light mode */
#themeswitch:not(:checked) ~ * .light,
#themeswitch:checked ~ * .dark {
  display: none;
}

label elements are used to add text to input elements. You can specify which input they belong to using the for attribute.
Clicking a label activates the input it belongs to, no matter where on the page either of them are.

And that's the theme switch all done! This technique can also be used for many other effects, maybe your website shows playing cards that you can flip over, or you want a toggleable sidebar, the possibilities are endless.

Modal

Since 2022, the dialog element has been available in all major browsers. This element allows developers to create modals without having to implement them theirselves. It has one major flaw though: While you can close them with just a form, you still need Javascript to open them.

To get around this, we're going to implement the modal ourselves anyway, so we can use our checkbox trick again:

<input class="modal-switch" id="modal-img" type="checkbox">
<div class="modal">
  <img src='images/big.png'>
</div>
.modal-switch {
  display: none;
}

.modal-switch:not(:checked) + * {
  display: none;
}

.modal {
  /* This makes the modal stretch the entire screen */
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;

  /* This centers the modal's content */
  display: flex;
  align-items: center;
  justify-content: center;

  background-color: #0000007f;
  /* This makes sure the modal is drawn over everything else */
  z-index: 999;
}

Note that we use + here instead of ~, which only matches the first element after the checkbox, instead of all of them.
Another thing to note is that there is no selector for when the modal should be shown. This is so we can simply set display: flex; on the modal itself, which will only get overridden when the modal is closed.

Let's add a way to open the modal:

<label for="modal-img">
  <img src="images/small.png">
</label>

We now have an image we can click to view a bigger version of it, but there's a problem: When we open the preview, we can't close it anymore, because the modal is covering the label! Let's fix that:

<div class="modal">
  <img src='images/big.png'>
  <label class="backdrop" for="modal-img"></label>
</div>
.modal .backdrop {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  /* Move this line from .modal to here */
  background-color: #0000007f;
  /* This places the backdrop under everything else in the modal */
  z-index: -1;
}

If you click outside of the image now, it should close the modal.

Pages

You may have noticed that this website consists of multiple pages you can navigate to without reloading anything. I claim this website uses no Javascript anywhere, and checkboxes can only have two states, so how did I do that?

Well, it turns out radio buttons are perfect for that. Let's create a simple site with three pages:

<div class="stack">
  <div class="page-container">
    <input class="page-switch" type="radio" name="pageswitch" id="to-home" checked>
    <div class="page" id="home">
      <!-- homepage content -->
    </div>
  </div>

  <div class="page-container">
    <input class="page-switch" type="radio" name="pageswitch" id="to-about">
    <div class="page" id="about">
      <!-- about page content -->
    </div>
  </div>

  <div class="page-container">
    <input class="page-switch" type="radio" name="pageswitch" id="to-contact">
    <div class="page" id="contact">
      <!-- contact page content -->
    </div>
  </div>
</div>
.stack {
  display: grid;
}
.stack > * {
  grid-area: 1 / 1 / 2 / 2;
  min-width: 0;
  min-height: 0;
}

/* This hides the radio buttons */
.page-switch {
  display: none;
}

/* And this hides unselected pages */
.page-switch:not(:checked) ~ * {
  display: none;
}

It is important to note that the name attribute of the radio buttons should be the same for the entire group, and should be different for each group, in case you're adding multiple elements with tabs.
You should also make sure one of your radio buttons has the checked attribute, so the user doesn't see an empty screen upon loading the page.

We have our pages, now we need a way to navigate to them. Let's create a navbar with links for our pages:

<nav>
  <label for="to-home">Home</label>
  <label for="to-about">About</label>
  <label for="to-contact">Contact</label>
</nav>

<div class="stack">
  <div class="page-container">
  <!-- etc -->

These don't need any specific styling, but we could do something like this:

nav {
  display: flex;
  align-items: center;
  gap: 8px;
}

nav label {
  cursor: pointer;
}

nav label:hover {
  text-decoration: underline;
}

Now we can navigate to our different pages instantly, without having to load anything. There is just one thing missing: how do we know on what page we are? That's where it gets more complicated.

The only good option we have for this is :has(), which checks if an element can be found, but doesn't actually match it. The problem with :has(), is that it's quite a new feature, and older browsers won't support it. This is also why I used ~ instead of something like :has(:target), despite the latter being able to start on a certain page based on the url.
For those who do have support for it, we can change our css like this:

/* old */
nav label:hover {
  text-decoration: underline;
}

/* new */
body:has(#to-home:checked   ) nav label[for='to-home'   ],
body:has(#to-about:checked  ) nav label[for='to-about'  ],
body:has(#to-contact:checked) nav label[for='to-contact'],
nav label:hover {
  text-decoration: underline;
}

Now our users shouldn't get lost.

Caveats

Not having to rely on Javascript without losing any features might sound like it's too good to be true, because it is.

Size

Bundling every single page into a single document means that the browser has to download your entire website before showing a single page. If you don't have that many pages that's fine, but if your website contains a blog like this, it adds up rather quickly.
That said, many single page websites using Javascript seem to also consist of one big bundle instead of cleverly loaded chunks, taking up even more space.

Accessibility

The techniques in this post obviously use elements in unintended ways, which can cause problems with the accessibility of your website. Making an accessible website should be possible, but I haven't looked into it myself, so that will be a future update to the website, and potentially another blogpost.