March 10, 2022

How to make a button - Normalisation

The first step towards a truly awesome button is normalisation! Today we make all our elements look the same.

How to make a button - Normalisation
Click here for the start of this series

Step 1: Normalisation

This is the first and most crucial step in creating reusable buttons that people get wrong the most, as typically, a developer will take a <button class="cool-button" /> apply some padding + background + border and call it a day.

This is the wrong approach because what happens two days later when that form submitting button, that the managers just loved to pieces, now needs to be used to create a link from /categories over to /categories/pop. Well, the developer will start with <a class="cool-button" /> and suddenly realise that their button has a weird colour when it's hovered, that it gets an underline and finally that the display is inline instead of an inline-block. What they didn't discover is that once the user goes to that link, the button text changes colour to, unfortunately, a shade very similar to their button's custom styling, which they never noticed because they were working backwards trying to force an element that already has pre-existing looks and feels into a class that doesn't allow for it. What is worse is that depending on how this we implement, it could make the original button not look right. Plus, when we start to use more than one different colour button, it can get super hard to keep bending the unplanned css design to bow to the system's needs.

This is why it's always best to start with a baseline, that baseline being a normalised span of text with 0 browser styling and 0 custom styling (aka, if you are using tailwind, it's time to apply some additional work to make sure your button looks like plain old boring text as possible.).

See what that looks like here:

/* define default button theme on root */
:root {
--btn-color: #FFFFFF;
}
/* normalise base element */
.anchor-btn {
display: inline-block;
text-decoration: unset;
color: var(--btn-color);
}
.button-btn {
display: inline-block;
background: none;
border: none;
padding: 0;
font: inherit;
color: var(--btn-color);
}

As you can see, the button and the anchor tag both look identical to the span tag; they are normalised and now ready to receive the button styling. You might have also noticed "--btn-color: #FFFFFF" and also "color: var(--btn-color);" what this is, is the start of how we apply theming to our buttons. It boils down to: "color: #FFFFFF" however, we use the css variable "--btn-color" as a way to allow future styles further down in the stylesheet (and therefore with higher precedence) to overwrite its meaning and thus change the color of the text.

But why not just do this in the button styling instead of the normalisation? It feels like the colour of a button should be the concern of the styling step and not the normalisation! The Hot Pink colour of our button is a part of its look and feel. However, there is some reason behind this madness, which is that not everybody has a shadow dom like I do to render their buttons inside of (maybe you are using web components and do?), which sometimes means some pretty nasty styles can pollute the global styling of your elements. Say, for example, what if somebody decided to make links that are hovered red:

a:hover {
color: red !important;
}

This is such a big deal when it comes to our button (and in general, all css, please, for the love of all things holy, stop using !important and start planning out your specificity) because now our button styling must include a fix for the anchor tags un-normalised look and feel. This is an issue purely because it pollutes the styles section of your css and makes it less and less about the buttons look and feel and more about how the anchor tag looks and feels.

By using css variables when we apply our normalisation fix on our anchor tag to fix the above:

/* higher up in the stylesheet */
a:hover {
color: red !important;
}
/* our button styling */
.anchor-btn {
--btn-color: #FFFFFF;
display: inline-block;
text-decoration: unset;
color: var(--btn-color);
}
a.anchor-btn:hover { /* more specific so the !important here will apply */
color: var(--btn-color) !important;
}

What's also nifty about using css variables here is that it also means that when somebody implements a custom button, they don't have to remember also to add the fix as shown above as they are already setting "--btn-color" so therefore our fix in the normalisation layer will come along for their custom look for free!

:root and Global css-variables

If you wonder what the ":root" thing is at the top, this is just a pseudo selector that selects the document's root (HTML element) and defines your css variable globally. Your first gut reaction might be one of shock and horror, we just spent the last ten years migrating to ESM to get away from global name conflicts, and now we go and start standardising on them! The concern is correct; however, the general rule of thumb with a best practice is: as soon as you know why the best practice of avoiding globals exists (i.e. name clashing), you can feel some form of safety in using them, provided you keep that in mind. This is why we didn't just call our "--btn-color" variable "--color" as the author of the website; I take ownership of the global css variables, and as such, I would never allow for any other library to be utilised which haphazardly defined css variables which were overly generic and in doing so I am left free to use them.

To better protect ourselves against global name conflicts, we could also choose to prefix our variables much like how bootstrap uses "--bs-" as its prefix. I could use "--mcl-"; however, I am yet to run into issues with 3rd party libraries also using my button variable names, and I have good refactoring tools for if that day were ever to arise.

What are the gains with all the risks of this global definition of my button's variable? Easy, the ability to customise theming of sections of my dom. Say, for example, I want to make the sidebar's button text colour hot pink? Easy I define the aside like so:

<style>
aside {
--btn-color: #FF69B4; /* or <aside style="--btn-color: #FF69B4;"> */
}
</style>
<aside>
Look at my <button class="btn button-btn">HOT PINK</button> button
</aside>

At a later point, we will go into more detail on button customisation , but the above is a quick glimpse into the tremendous power they can provide.

Next Up: part 3 - styling

Tagged with css, design, html, --css-variables

something not right? Open a PR