Considerations for Making a CSS Framework

Avatar of Tahmid
Tahmid on

Take the pain out of building site search with the Algolia hosted API. Start free now!

Around eight months ago, I started building a framework which would eventually go on to become Halfmoon. I made a post on this very website announcing the launch of the very first version. Halfmoon has been billed as a Bootstrap alternative with a built-in dark mode feature, that is especially good when it comes to building dashboards and tools. All of this still applies to the framework.

However, today I would like to talk about an area of the framework that is a bit understated. I believe our industry as a whole seriously underestimates the value of customization and user personalization, i.e. users being able to set their own design preferences. Chris has written before about knowing who a design system is made for, pointing out a spectrum of flexibility depending on who a system is meant to help.

But it’s more than design systems. Let’s talk about how Halfmoon addresses these issues because they’re important considerations for knowing which framework works best for your specific needs.

Dashboard built using Halfmoon

Who is Halfmoon for?

Before diving in, let’s address an important question: Is Halfmoon the right framework for you? Here’s a list of questions to help you answer that:

  • Are you building a dashboard, tool, or even a documentation website? Halfmoon has many unique components and features that are specific to these use cases.
  • Are you familiar with Bootstrap’s class names, but wish that the design was a bit more premium-looking?
  • Does your users want or expect a dark mode on your website?
  • Do you dislike dependencies? Halfmoon does not use jQuery, and also has no build process involving CSS preprocessors. Everything is pure, vanilla CSS and JavaScript.
  • Are you tired of dealing with complex build systems and front-end tooling? This ties in to the previous point. Personally, I find it difficult to deal with front-end tooling and build processes. As mentioned above, Halfmoon has no build process, so you just pull in the files (local, CDN, or npm), and start building.

If you answered yes to any (or all) of these questions, you should probably give Halfmoon a try. It is important to note however, that Halfmoon is not a UI component library for React/Vue/Angular, so you shouldn’t go into it expecting that. Moreover, if you are more fond of purely utility driven development, then Tailwind CSS is a better option for you. When it comes to CSS utilities, Halfmoon takes a middle of the road approach – there are utilities plus semantic classes for common components.

Using CSS custom properties

First, let’s get the easy stuff out of the way. CSS custom properties are incredible, and I expect them to completely replace preprocessor variables in the future. Browser support is already at a solid ~96%, and with Internet Explorer being phased out by Microsoft, they are expected to become a standard feature.

Halfmoon is built entirely using CSS variables because they provide a huge degree of customization. Now, you might immediately think that all this means is that there are a few custom properties for colors sprinkled in there, but it’s more than that. In fact, there are over 1,500 global variables in Halfmoon. Almost everything can be customized by overriding a property. Here’s a nifty example from the docs:

Halfmoon customization using CSS variables
Swapping out a few custom property values opens up a ton of possibilities in Halfmoon, whether it’s theming things for a brand, or tweaking the UI to get just the right look.

That’s what we’re talking about here when it comes to customization: does the system still stand up and work well if the person using it overrides anything. I have written extensively about this (and much more) in the official Halfmoon docs page.

Variables aren’t a new concept to frameworks. Many frameworks actually use Sass or Less variables and have done so for quite a while. That’s still a good and effective way to establish a customizable experience. But at the same time, those will lock into a preprocessor (which, again, doesn’t have to be a bad thing). By relying instead on CSS custom properties — and variable-izing all the things — we are relying on native CSS, and that doesn’t require any sort of build dependency. So, not only can custom properties make it easier to customize a framework, but they are much more flexible in terms of the tech stack being used.

There is a balance to be had. I know I suggested creating variables for everything, but it can be equally tough to manage and maintain scores and scores of variables (just like anything else in the codebase). So, lean heavily on variables to make a framework or design system more flexible, but also be mindful of how much flexibility you need to provide and whether adding another variable is part of that scope.

Deciding what components to include

When it comes to building a CSS framework, deciding what components to include is a big part of that ordeal. Of course, for a developer working on a passion project, you want to include everything. But that is simply not feasible, so a few decisions were made on my part.

As of right now, Halfmoon has most of the components you can find in similar frameworks such as Bootstrap or Bulma. These frameworks are great and widely used, so they are a good frame of reference. However, as I have mentioned already, a unique thing about Halfmoon is the focus on building tools and dashboards on the web. This niche, if you could call it that, has led me to build some unique components and features:

  • 5 different types of sidebars, with built-in toggle and overlay handlers. Sidebars are very important for most dashboards and tools (and a pain to get right), so this was a no brainer.
  • 2 different types of navbars. There is one that sticks to the bottom of the page, which can be used to great effect for action buttons. Think about the actions that pop up when you select items on data-table. You could place those action buttons here.
  • Omni-directional dropdowns (with 12 different placements, 3 for each direction).
  • Beautiful form components.
  • Built-in keyboard shortcut system, with an easy way to declare new ones for your tool.
  • Tons of utilities. Of course, this is not comparable to Tailwind CSS, but Halfmoon has enough responsive utility classes to handle a lot of use cases right out of the box.

Moreover, the built-in dark mode, huge customizability, and the standard look and feel to the components, should all work together to make Halfmoon a great tool for building web tools and dashboards. And I am hopefully nowhere close to being done! The next updates will bring in a form validator (demo video), more form components, multi-select component, date and time picker, data-table component, etc.

So what is exactly missing from Halfmoon? Well the most obvious ones are tabs, list group, and spinners. But all of these are planned to be added in v1.2.0, which is the next update. There are also other missing components such as carousels, tree navigation, avatars, etc, which are slightly out of scope.

Providing user preferences

Giving end users the ability to set their preferences is often overlooked by frameworks. Things like setting the font size of an article, or whether to use a dark or light theme. In some ways, it’s sort of funny, because the web is catching up to what operating systems have allowed users to do for decades.

Here are some examples of user personalization on the web:

  1. Being able to select your preferred color mode. And, even better, the website automatically saves and respects your preference when the page is loaded. Or better yet, looking at your operating system preferences and automatically accommodating them.
  2. Setting the default size of elements. Especially font size. A small font might look good in a design, but allowing users to set their ideal font size makes the content actually readable. Technically, every modern browser has an option to zoom into content, but that is often unwieldy, and does not actually save your settings.
  3. Setting the compactness of elements. For example, some people prefer large padding with rounded corners, while others find it a waste of space, instead preferring a tighter UI. Sort of like how Gmail lets you decide whether you want a lot of breathing room in your inbox or make it as small and tight as possible to see more content.
  4. Setting the primary color on the website. While this is entirely cosmetic, it is still charming to be able to set your favorite color on every button and link on a website.
  5. Enabling a high contrast mode. Someone pointed this out to me on GitHub. Apparently, many (and I mean many) CSS frameworks often fail the minimum contrast recommended between foreground and background colors on common elements, such as buttons. That list includes Halfmoon. This is often a tradeoff, because overly contrastive elements often look worse (purely in terms of aesthetic). User personalization can allow you to turn on a high contrast mode, if you have difficulty with the default contrast.

Allowing for user personalizations can be really difficult to pull off — especially for a framework — because that would could mean swapping out huge parts of CSS to accommodate the different personalization settings and combinations. However, with a framework like Halfmoon (i.e. built entirely using CSS variables), this becomes trivial as CSS variables can be set and changed on run-time using JavaScript, like so:

// Get the <html> tag (for reading and setting variables in global scope)
var myElement = document.documentElement;

// Read CSS variable
getComputedStyle(myElement).getPropertyValue("--variable-name");

// Set CSS variable
myElement.style.setProperty("--variable-name", "value");

Therefore, user personalization can be implemented using Halfmoon in the following way:

  • The user sets a preference. That basically means a variable value gets changed. The variable is set with JavaScript (as shown above), and the new value is stored in a cookie or local storage.
  • When the user comes back to the website, their preferences are retrieved and set using JavaScript (again, as shown above) once the page is loaded.

Here are visual examples to really hammer the point home.

Setting and saving the default font size

In the example above, whenever the range slider is changed, the variable --base-font-size is updated to the slider’s value. This is great for people who prefer larger text. As explained in the previous section, this new value can be saved in a cookie or local storage, and the next time the user visits the website, the user preference can be set on page load.

Setting the compactness of content

Compact theme using CSS variables
Because there are CSS custom properties used as utilities, like spacing and borders, we can remove or override them easily to create a more compact or expanded component layout.

Only two variables are updated in this example to go from an expanded view to a compact one:

  • --content-and-card-spacing changed from 3rem (30px) to 2rem (20px).
  • --card-border-radius changed from 0.4rem (4px) to 0.2rem (2px).

For a real life scenario, you could have a dropdown that asks the user whether they prefer their content to be Default or Compact, and choosing one would obviously set the above CSS variables to theme the site. Once again, this could be saved and set on page load when the user visits the website on their next session.

Wait, but why?

Even with all the examples I have shown so far, you may still be asking why is this actually necessary. The answer is really simple: one size does not fit all. In my estimate, around half of the population prefers a dark UI, while the other half prefers light. Similarly, people have wild variations about the things they like when it comes to design. User personalization is a form of improving the UX, because it lets the user choose what they prefer. This may not be so important on a landing page, but when it comes to a tool or dashboard (that one has to use for a long time to get something done), having a UI that can be personalized is a boon to productivity. And knowing that is what Halfmoon is designed to do makes it ideal for these types of use cases.

Moreover, you know how people often complain that websites made with a certain framework (eg Bootstrap) all look the same? This is a step toward making sure that websites built with Halfmoon will always look distinct, so that the focus is on the website and content itself, and not on the framework that was used to build it.

Again, I am not saying that everything should be allowed to be personalized. But knowing who the framework is for and what it is designed to do helps make it clear what should be personalized.

Looking ahead

I strongly feel that flexibility for customization and accounting for user preferences are often overlooked on the web, especially in the framework landscape. That’s what I’m trying to address with Halfmoon.

In the future, I want to make it a lot easier for developers to implement user preferences, and also promote diversity of design with new templates and themes. That said, here are some things on the horizon for Halfmoon:

  • A form validator (demo video)
  • New components, including range sliders, tabs and spinners
  • High contrast mode user preference
  • Multi-select component (like Select2, only without jQuery)
  • A date and time picker
  • A data-table component
  • A GUI-based form builder
  • More themes and templates

You can, of course, learn more about Halfmoon in the documentation website, and if you want to follow the project, you can give it a star on GitHub.