Upgrading to Bulma v1

Learn about my experience upgrading my website to Bulma v1, featuring the new built-in Bulma dark mode.

Published: Updated:

8 min read

Abstract illustration of before and after website upgrade to Bulma v1

Introduction

For those who aren’t aware, this site has been styled with Bulma. Bulma is a pure CSS frontend framework used for adding style to web pages. Bulma is a personal favorite of mine because it’s easy to get started with, looks good, and doesn’t rely on JavaScript. Up until now, I was using version 0.9.4.

Previously, I had written about building this site with Bulma in a blog post titled Creating a Blog with Bulma & Gatsby: The Power of Dark Mode. At the time, Bulma didn’t support dark mode, so I had to come up with a custom version of it. Fortunately, one of the big changes in v1 is built in Bulma dark mode.

While I am tagging this as a migration, it’s really just an upgrade. Compared to my other migrations, this one has been pretty straightforward. Though there were a few surprises, the overall process was relatively painless.

Anyway, I’m going to talk about what it was like making the upgrade from Bulma version 0.9.4 to 1.0.0.

Motivation for the Upgrade

Since I had first learned about Bulma’s v1 release, I was eagerly looking forward to the opportunity to upgrade my website. The main thing which caught my eye was the built-in dark mode support. Having gone through the effort of developing my own solution to that, I was excited to simplify my codebase. Other features like skeleton loaders and variables also caught my eye. Skeleton loaders are an interesting addition to me because I want to find ways to use them where images are supposed to load in.

Surprises

I encountered a few unexpected challenges:

  • Changes in the appearance of components necessitated some structural changes to pages.
  • The margins of some components don’t appear to be the same, so I had to make adjustments.
  • The way to access mixins is different.
  • The new addition of dark mode impacted the JavaScript I had written previously.

Updating Existing Bulma Dark Mode Code

One thing I did was change the structure of the Bulma dark mode toggle switch I had made previously. Here’s what the code looks like now:

---
import { library, icon } from '@fortawesome/fontawesome-svg-core';
import { faSun, faMoon } from '@fortawesome/free-solid-svg-icons';

library.add(faSun);
library.add(faMoon);

const sunIcon = icon(faSun).html;
const moonIcon = icon(faMoon).html;
---

<button class="button" id="dark-mode-toggle">
  <span class="icon">
    <i id="dark-mode-icon" class="is-flex is-align-content-center nav-icon-wrapper" set:html={moonIcon}></i>
    <i id="light-mode-icon" class="is-flex is-align-content-center nav-icon-wrapper" set:html={sunIcon}></i>
  </span>
</button>

<!-- There's an inline script tag here -->

You can see I’m injecting some Font Awesome icons. I’ll talk about that more in a later section. I’m omitting the details of the script code here for brevity, but the key changes involve targeting new theme classes:

if (isLight) {
  document.documentElement.classList.remove('theme-dark');
  document.documentElement.classList.add('theme-light');
  document.getElementById('dark-mode-icon').classList.add('hide');
  document.getElementById('light-mode-icon').classList.remove('hide');
} else {
  document.documentElement.classList.add('theme-dark');
  document.documentElement.classList.remove('theme-light');
  document.getElementById('dark-mode-icon').classList.remove('hide');
  document.getElementById('light-mode-icon').classList.add('hide');
}

Updating the Table of Contents

The table of contents component I have on my blog posts needed to be changed. Because of the drop shadow effect and other changes to how Bulma looks, I had to make some modifications. The table of contents no longer uses the box class, and now uses the menu class. Here’s what it looks like now:

<div class="table-of-contents pt-6 hide-from-touch">
  <div class="mt-6 menu">
    <p class="menu-label">Table of Contents</p>
    <ul id="table-of-contents" class="menu-list">
      {
        toc.map(heading => <TableOfContentsHeading heading={heading} />)
      }
    </ul>
    <progress id="scroll-progress" class="progress is-small is-primary mt-3" value="0" max="100">0%</progress>
  </div>
</div>

I also had to add a little bit of padding because when you switch between light and dark mode, there’s a little flash on the list items. Adding the space makes them look better.

Changes to Sass

Lastly, I had to update one of my CSS classes which was using a Bulma mixin. My class previously looked like:

.hide-from-touch {
  @include until($desktop) {
    display: none !important;
  }
}

As the name implies, this class can make elements disappear on touch screens. I use for making the table of contents disappear when a non-desktop user is on the site.

Here’s what the new code looks like:

@use "bulma/sass/utilities/mixins";
@use "bulma/sass/utilities/initial-variables";
@use "bulma/bulma";

.hide-from-touch {
  @include mixins.until(initial-variables.$desktop) {
    display: none !important;
  }
}

Benefits

The benefits I’ve seen:

  • The default light and dark themes look really good, in my opinion.
  • The amount of custom code I have around Bulma has drastically reduced. I’m not going to show exactly what the reductions were (because it’s a lot), but I was able to delete 248 lines of Sass code.

While it’s a short list, I think the code reduction alone has made the upgrade worth it. Removing those 248 lines of Sass code is useful to me because it reduces the complexity of my site’s custom styles which makes maintenance easier.

Insights and Reflections

Updating from tiles to the new smart grid system was pretty straightforward. I used tiles for my photo gallery and the smart grid looks the same.

For whatever reason, it was good for me to start working on this because I felt inspired to also tackle a problem which had been affecting the icons loading in the menu bar.

Side Quest: More Efficient Icon Loading

With the adoption of the dark mode changes, I am now using more icons from Font Awesome than I was before. Previously, I was using dev, RSS, link, and arrow-up-right-from-square. However, as part of the upgrade I added two more icons. The first is sun and the second is moon. With the Bulma upgrade, I decided to tackle a small problem related to icons which had been bugging me.

Unfortunately, I was loading up the entirety of Font Awesome just to use those icons. This would lead to icon pop-in on some browsers like Safari. The icon pop-in problem was irritating because it would show a blank square before the icon would load in. AWS Amplify (my hosting provider) seems to also not be caching all the resources correctly to offset this problem.

To offset this issue, I decided to look into ways to load the icons more efficiently. I decided to ask ChatGPT for help with this, and it wrote the following code snippet:

// Import FontAwesome core functionality
import { library, dom } from '@fortawesome/fontawesome-svg-core';

// Import only the GitHub icon from the brands icon package
import { faGithub } from '@fortawesome/free-brands-svg-icons';

// Add the imported icon to the library
library.add(faGithub);

// Tell FontAwesome to watch the DOM and add the SVGs when it finds icons
dom.watch();

This approach was not suited for my setup, as I’m using Astro and the operations are not client-side. Of course, that was sort of my fault since I didn’t make it clear what the project set up was. After some research I found this GitHub comment on the Font Awesome repo. The user who posted seemed to be going with a client-side approach. After some experimentation I ended up with the following:

---
import { icon } from '@fortawesome/fontawesome-svg-core';
import { faDev } from '@fortawesome/free-brands-svg-icons';
import { faSquareRss } from '@fortawesome/free-solid-svg-icons';

const devIcon = icon(faDev).html;
const rssIcon = icon(faSquareRss).html;
---

<!-- Some other code is here -->
<a href="/rss.xml" class="nostyle mr-3 nav-icon-wrapper" set:html={rssIcon}>
</a>
<a
  href="https://dev.to/logarithmicspirals"
  class="nostyle mr-3 nav-icon-wrapper"
  aria-label="dev.to"
  target="_blank"
  rel="noreferrer"
  set:html={devIcon}
>
</a>
<!-- Some other code is here -->

However, it seems like the width and height attributes need to be set for the SVG icons to be visible. Since this doesn’t appear to be included by default, I ended up adding some custom CSS to do it:

.svg-inline--fa {
  width: 1.33em;
  height: 1.33em;
}

Another option would be to do a string modification of the HTML in the code fence, but I didn’t feel like that was the right approach. I ended up leaving a comment on the GitHub issue with my findings.

Now, there’s one additional piece to this which I had to address and that is the icon classes which were being inserted automatically to external links in my blog posts. The blog posts are written in markdown and parsed with rehype.

To get the icons back into the heading and external links, I had to install hast-util-from-html-isomorphic. I was able to figure this out by reading the README for rehype-autolink-headings. Then I had to update my astro.config.mjs file with the following:

// ...
import { fromHtmlIsomorphic } from 'hast-util-from-html-isomorphic';

import { icon } from '@fortawesome/fontawesome-svg-core';
import { faLink, faArrowUpRightFromSquare } from '@fortawesome/free-solid-svg-icons';

const linkIcon = icon(faLink);
const arrowUpRightFromSquareIcon = icon(faArrowUpRightFromSquare);

// ...

export default defineConfig({
  // ...
  markdown: {
    // ...
    rehypePlugins: [
      // ...
      [
        // ...
        {
          behavior: 'append',
          content: {
            type: 'element',
            tagName: 'i',
            children: fromHtmlIsomorphic(linkIcon.html[0], {fragment: true}).children,
            properties: {
              class: 'heading-link'
            }
          },
          properties: {
            class: 'title-link'
          }
        }
      ],
      [
        rehypeExternalLinks,
        {
          target: '_blank',
          rel: 'noopener noreferrer',
          test: externalLinkTest,
          content: {
            type: 'element',
            tagName: 'i',
            properties: {
              class: 'external-link ml-1'
            },
            children: fromHtmlIsomorphic(arrowUpRightFromSquareIcon.html[0], {fragment: true}).children
          }
        }
      ]
    ]
  },
  // ...
});

As an aside, externalLinkTest is a custom function I wrote previously to determine if a link is pointing to an external domain. After updating the config, I had to go and make some changes to my CSS to handle the difference between markdown icons and navbar icons:

.nav-icon-wrapper > .svg-inline--fa {
  width: 1.33em;
  height: 1.33em;
}

.heading-link > .svg-inline--fa {
  width: 1.5rem;
  height: 1.5rem;
}

.external-link > .svg-inline--fa {
  width: 0.7rem;
  height: 0.7rem;
}

Conclusion

Upgrading to Bulma v1.0.0 has not only streamlined my site with built-in features like Bulma dark mode but also significantly reduced the complexity of the code, making maintenance easier. This process underscored the importance of keeping up-to-date with technology to enhance both user experience and development practices. Facing unexpected challenges, such as optimizing icon loading, provided practical insights into efficient resource management, demonstrating the continuous evolution necessary in web development.


profile

Welcome to my blog! I am a software engineer based in Southern California, and I love sharing my thoughts and experiences about all things tech. From software development and programming to the latest tech trends and news, you’ll find it all here on my blog. Follow along to stay up to date and get insights from a real-life software engineer living and working in SoCal. Thanks for visiting!

New post!

Building a Social Media Landing Page with Astro, Bulma, and Cloudflare

Click here to read more!