The many things that using and relying on CSS frameworks alone won’t teach you

Ifeora Okechukwu
16 min readMay 2, 2023

CSS is a very declarative and expressive yet easily misunderstood visual language. I have often maintained that CSS is easy to learn but hard to use. It needs the full cooperation of markup (HTML) and also a deep understanding of CSS layered approach to styling to get things done well. This is one of the many things that most people who dislike writing CSS don’t appreciate about CSS. It is the job of the developer (frontend) to set the stage for CSS and markup (HTML) to both play nice. But sometimes, this job suffers due to a lack of clarity.

The expressive parts of a CSS declaration

Ever since CSS frameworks made it to mainstream web development from (Bootstrap 2.x–5.x), (Foundation 0.x), (Primer 1.x — 21.x), (Bulma 0.x) all the way now to (Tailwind 1.x-3.x), It has been a rollercoaster ride to work with each of them. There has been serious low points and also much serious high points in the evolution and journey of the CSS ecosystem from the introduction of methodologies like BEM or ITCSS (i very much encourage you to learn about and use ITCSS) to the abuse of the !important rule or the over-utilisation of utility classes, frontend developers have found their way through it all. However, there are tiny salient helpful tips for writing CSS in a much stress-free and scalable way that have gone almost unknown by many practitioners for a long while. Only very few CSS developers in the world take advantage of them.

Today, i want to share some of these tips with you. So, buckle up and strap in tight! It’s gonna be fun.

Here we gooo…. 🎉🎉🎉

Before you continue reading this article, there are a few terms and concepts i assume you should be aware of prior. Please, if you don’t have knowledge of these, i urge you to take a second to read them up (or skim through them — to refresh your memory). These terms and concepts will be important to the rest of this article and are as follows: Specificity, Block-Level, Source Order, CSS Positioning, Quirks Mode, CSS RuleSet, CSS Layout Containers.

This article also assumes that you know the basic CSS properties and some of their values and require no primer or formal introduction to them.

Finally, after reading up on these terms and concepts with full understanding, you can continue reading this article presently.

Never add a relative positioning to the body tag 🤨

This first principle is straightforward and adhering to it will save you stress in the future. You can however set position: relative; on the html tag if you want to (depending on the design you’re working on or you just want tag that are positioned absolutely without insets not flying off the screen) but never ever do so on the body tag. Why ? It’s is the reference for all absolutely positioned element anyway so it serves no purpose. Also, setting position:relative; on the body seems to be a relic from the IE v5.x (Internet Explorer 5) days for IE v5.x in quirks mode and fix a nasty bug. Finally, it helps if you don’t mess with the rules concerning containing blocks by adding a position: relative; to the body tag.

Always add margins and borders indirectly to any element 🤠

Adding margins directly to any element using CSS is any easy win in the short-term but a horrific decision in the long-term. The reason is simple: You have to do a lot of work later on to clean-up and override the margins when you use that same element in a different context mostly under a different parent tag (read as parent selector).

/* DON'T DO THIS !!! */

.call-to-action {
margin-left: 20px;
border: 2px solid cyan;
cursor: pointer;
}

/* When you want to use the button in a specific or different context
from the earlier one, you have to override the margin defined earlier */

.newsletter .call-to-action {
margin-right: 40px;
/* The margin-left of 20px is still active*/
margin-left: 10px;
border: 1px solid brown;
}

So, it’s much safer, better, stress-free and cleaner to add margins indirectly via a parent selector like so:

/* DO THIS INSTEAD !!!!!!! */

.call-to-action {
cursor: pointer;
}

/* `.card-wrapper` is the parent selector */

.newsletter .call-to-action {
margin-right: 40px;
/* The margin-left of 20px is still active*/
margin-left: 10px;
border: 1px solid brown;
}

.contact-form .call-to-action {
margin-left: 20px;
border: 2px solid cyan;
}

Only use unit-less number values for line heights 🤓

Well, it’s commonplace to setup line-height using unit values like px, % or em. However, it’s actually not advised to use unit-based values as a value for this CSS property. This is because the value of line-height is closely tied to the font-size of the element on which it is declared and can create some unexpected results.

/* DON'T DO THIS */

body {
line-height: 5px;
}

/* DO THIS INSTEAD */

body {
line-height: 1; /* No units */
}

See more in this MDN excerpt.

Always set properties inherited by default on the closest parent to element (block or inline) to the target text node(s) or on the target element itself 😊

When it comes to all CSS properties are inheritable (by default), there are only two categories (as all of them are used for either one of two types of formatting):

  1. Display formatting (display variations — e.g. empty-cells, caption-side, visibility, border-collapse, list-style-type, position, overflow, cursor, opacity)
  2. Visual formatting (content variations — e.g. font-size, font-weight, font-style, font-variant, line-height, widows, orphans, text-align, color, cursor, direction, text-transform, letter-spacing, quote)

The rule of thumb is to always ensure that you define inheritable CSS properties on the closest block-level element to the child element containing text or an inline elements’ child element which you are targeting with those inheritable styles.

For instance: I see people do this:

/* TOO FAR FROM TARGET TEXT OR INLINE ELEMENT THAT. WOULDD NEED IT */

body {
quote: "«" "»" "‹" "›";
}

It should be this:

/* PERFECT! */

q {
quotes: "«" "»" "‹" "›";
}

q::before {
content: open-quote;
}

q::after {
content: close-quote;
}

Another example:

/* AGAIN, TOO FAR FROM TARGET TEXT OR INLINE ELEMENT */

body {
color: black;
text-align: center;
min-height: 100%;
}

The color and text-align should be defined on the element very close to the text that should have said style like so:

body {
min-height: 100%;
}

span {
color: black;
text-align: center;
}

There’s an exception to this guideline however, which is for font-family and maybe line-height on the body tag.

body {
font-family: sans-serif;
}

Now, in the sprit and promise of CSS, you can collect and factor out all inheritable CSS properties and rulesets (common to a set of nested elements) to elements much higher up in the nesting hierarchy. However, you should only do this after properly establishing clear and concrete commonalities in these elements that would share this inheritable CSS.

For example:

.comment {
font-size: 0.68rem;
letter-spacing: -0.0034em;
color: #333;
}

.citation {
font-size: 0.68rem;
text-indent: 0.0625em;
color: #333;
}

Now, from the above, you can see that both the .citation and .paragraph classes have some commonality in the inheritable CSS properties which can be factored out.

This will become 👇🏾👇🏾

.feed-block .comment,
.feed-block .citation {
font-size: 0.68rem;
color: #333;
}

.comment {
letter-spacing: -0.0034em;
}

.citation {
text-indent: 0.0625em;
}

I prefer going through the motions of clear detecting commonality over. time before factoring it out like so. Why ? because, i don’t want to employ the idea of removing obvious duplications (DRY) too early or too prematurely.

Only use absolute units for margins and borders 🤗

Never make it a habit to use px (pixel), ex, ch or centimeter units for all widths, heights, paddings and font-sizes. Why ? It doesn’t scale and can cause problems later on especially where responsive layouts are a requirement or you have to make the web page more legible on certain screens or media.

Use rem unit for paddings and font-sizes because they build on top of the user’s preferences and not override them the way using px does.

Also, use % units for widths and heights (dimensions) of block elements that are farther away from the body tag and vh or vw for block elements that are very much closer to the body tag or even the body tag itself. There are moments when you might want to use fixed units (e.g. px or ch) for widths or heights, for such moments, do not use the width property. Simply make use of the max-width or min-width property instead.

Furthermore, use em for visual formatting values that don’t relate directly to the font e.g. text-indent, word-spacing, letter-spacing as these have to be relative to the closest block-level parent of the element.

For instance:

/* `ch` is a '0' character fixed unit so, we don't use `width` to set it */

.textblock p {
min-width: 23ch; /* 23 '0' (zero) character width dimension */
}

/* I want to center this information card but want it to be
responsive by default as well */

.feed {
/* I can't use the 'width' property as i want to default responsiveness */
/* width: 600px; */

/* I don't want this card to overflow horizontally on mobile screens */
max-width: 600px;

margin: 0 auto;
background-color: #333333;
padding: 0 1.5rem;
}

Finally, never set widths or heights on buttons except you require them to fill up the entire horizontal space on either side. Use padding and font-size instead to size your buttons for different screen sizes: mobile, desktop, tv e.t.c.

For instance:

/*  !!! USING UNSEMANTIC CLASS NAMES FOR BREVITY !!! */

.button,
[role="button"] {
user-select: none;
cursor: pointer;
}

.button.x-small { /* No width or height used for sizing the button */
padding: 0.1rem 1.8rem;
font-size: 0.7rem;
}

.button.small { /* No width or height used for sizing the button */
padding: 0.2rem 2.1rem;
font-size: 0.9rem;
}

Lastly, you probably do not want to use viewport units beyond these 5 tags: <html>, <body> and <header>, <footer>, <main>.

Always effect full viewport height on body and html tags 💪🏾

Normal flow is a concept that doesn’t include vertical expansion of the content area/box of any block-level element. This is the first thing that doesn’t seems obvious when writing CSS for a particular web page in development. The reason why is that no one considers how it can become a hindrance later on. When you don’t setup the height of the body tag, you run into problems when you use fluid heights (in % only) on grand children tags (tags nested inside) of the body tag. If the body tag doesn’t fill the height of the viewport, then the tags inside the body don’t stretch to a height you’d probably desire (since % heights in CSS are relative to the parent tag) when coding up your CSS. If you use viewport units instead of percent units, then you can force the height of the nested tags in the body tag to stretch as you would like but it will be problematic when viewed on mobile or in a responsive situation.

There are many solutions to this out there including this one. However, i feel they are lacking. The only tag i feel comfortable setting viewport units on is the html tag. Every other tag can have a fluid dimension with a percent unit including the body tag. So my own solution is as setup here.

Never use CSS positioning when building page layout containers ❌

This is in fact a very important thing to take note of. When making layouts and setting up layout containers for a 2-column or 3-column web page, it’s important to make use of either flex box or grid layout systems and avoid using positioning (position: absolute; especially) to achieve what flex box or grid can also achieve. The problems you are guaranteed to run into when you want to start building in responsiveness to the web page is maddening.

Keep it really simple for layouts.

For example: 👇🏾👇🏾👇🏾

Take a look at this: https://codepen.io/isocroft/pen/mdxWdYP. If you look at the output of the pen, it looks like it has parts of the layout positioned (as fixed or absolute) but it’s not.

Always use closed-range CSS media queries 🤩

There are two broad ways to write your media queries: open-range and closed-range.

@media screen (max-width: 45rem) { /* OPEN-RANGE */
...
}

@media screen (min-width: 0rem) and (max-width: 45rem) { /* CLOSED-RANGE */
...
}

There seems to be a very innocuous difference between using one over the other but the amount of unnecessary clean-up work you give to yourself when you use open-range media queries isn’t worth it. The CSS rule you assign to a particular CSS selector spans multiple breakpoints when you use open-range media queries so you always have to override them at a larger breakpoint.

For instance:

/* For this media query, the margin of 20px applies to all breakpoints

like: 1024px, 1336px, 1680px and so on and so forth till infinity */

@media screen (min-width: 768px) {
.newsletter .call-to-action {
margin: 20px;
}
}

@media screen (min-width: 1024px) {
.newsletter .call-to-action {
/* The margin of 20px is still active at this breakpoint */

/* What if i don't want a margin on this element at all at
this breakpoint ? */

/* I have to override it here */

margin: 0; /* !!! unnecessary CSS code !!! */
}
}

But with closed range media queries, i don’t need to clean-up after myself as i write CSS especially when coding mobile-first solutions.

/* For this media query, the margin of 20px doesn't apply to all 

breakpoints */

@media screen (min-width: 768px) and (max-width: 1023px) {
.newsletter .call-to-action {
margin: 20px;
}
}

@media screen (min-width: 1024px) and (max-width: 1410px) {
.newsletter .call-to-action {
/* The margin of 20px is NOT active at this breakpoint */
}
}

!important — is a tool of last resort 😎

One of the reasons i really disliked Bootstrap v2 and v3 was the criminal and flagrant use of the !important rule. It was an eyesore to be honest. Overriding and building on top of the style definitions of Bootstrap 2 and Bootstrap 3 was a nightmare because of this. The !important rule helps you easily override CSS rule in a ruleset with much low selector specificity than in an existing ruleset. However, it should be use with caution and should be the very last resort after all other cleaner methods have been employed like reordering the source order of the CSS rulesets in question.

Read more here.

Say hello to shrink-to-fit triggers ✅

A shrink-to-fit trigger is a CSS property-value pair that causes the content area/box of an element to collapse its bounds or edges tightly around it’s contents and in so doing assumes the size of the sum of the sizes of it content. Very popular examples include display: inline-block OR display: inline-flex OR position: absolute OR width: fit-content. You can use shrink-to-fit triggers to safely make a block-level element (which may contain other block-level elements) to behave like an inline element (while also being able to expand in size horizontally) and allow other elements stay beside it on a web page. For instance, take a look at these 2 examples:

  1. https://codepen.io/isocroft/pen/gOOeqWa
  2. https://codepen.io/isocroft/pen/WNwgqOb

When writing great CSS, the form (style/look) should always follow the function (context/use case) and not the other way around🎉

There is a principle or guideline in architecture and industrial design that goes like this: Form follows function. It basically states that the design, style of a building should follow from what that building is meant for.

One can tell if a person is a novice with CSS from the way they write CSS. Usually, the aim for the novice is to just quickly style the element on the screen without any regard for its’ context or use case. This leads to creating a convoluted mess of distinct CSS ruleset definitions that need to be overridden too many times later on.

A good example of this (as was have mentioned earlier) is defining a CSS margin directly on an element. This is an attempt to style the element in its’ current context or use case without consideration for other contexts and use cases where it may appear once again. Now, you have to override the margin that was initially set for the former context to fit this newer context.

Another bigger example of this is the excessive use of utility classes (especially in CSS frameworks like Tailwind) and its many drawbacks. You see back in the day, during the days of HTML 3.2 and early days of HTML 4, we had presentational tags and attributes that were very similar in concept to utility classes e.g. <font> , <center> , <s>, <i> , bgcolor , cellpadding , color , align , valign . These tags and attributes defined styles for each element much the way inline styles are defined for an element. The issue with this back then was the sheer amount of needless repetition to make elements look, feel and be partially or totally alike.

I fear that with frameworks like Tailwind, it will become hard quite difficult to manage CSS definitions of sufficient scale and diverse use-case. Tailwind (and other like it) via utility-classes seem to have solved 4 problems with authoring CSS:

  • Duplicated Code — CSS styles that have been duplicated in error under 2 different selector names/titles
  • Dead Code — CSS styles that no longer referenced within markup because the markup that previously referenced them have been deleted over time.
  • Faster Development — CSS styles are applied quickly to the markup without the need to alternate frequently between the CSS file and HTML (or JSX) file.
  • Inconsistent Nomenclature for selectors — CSS styles are packaged well enough to avoid team members stepping all over themselves.

Furthermore, the cost of using utility classes excessively is felt elsewhere right in the markup with the needless repetitions, loads of unused utility classes (if you don’t use a build tool that’s hard to master) and nasty specificity issues. However, I do believe that utility classes have merit but only for things in CSS that do not change easily or often e.g. layout.

Let me explain better with an analogy: You see, building a web page using CSS is a lot like building a house. There are parts of the house that do not change easily or often like the structural works (e.g. the foundation, beams/lintels). Then, there are the parts that do change easily (e.g. the windows, doors, roofing, paint job) and can also be changed often. While using CSS, layouts (and other things related to structure) don’t change easily or often. However, colors, backgrounds, images, text and sizes can change easily and often.

Furthermore, If you want to name a class that has something to do with structuring content (width, height, min-height, max-width, e.t.c.) or setting up layout (flex, block, grid, margin, border e.t.c). Don’t spend too much time trying to come up with a semantic class name, simply utilize utility class names (or utility classes) for this. Only when you wish to do things around visual formatting (e.g. text-transform, font-weight, color, text-align, background-image, background-color, text-indent, font-style, text-decoration) and display styling or formatting (e.g. list-style-type, border-collapse, visibility) you should utilize clear semantic class names.

I believe that using proper Semantic classes with a mix of functional attribute selectors for the things that change easily and often while using Utility classes for the things that hardly change, will bring about better CSS authoring that we can rely on even at scale.

For example: 👇🏾👇🏾👇🏾👇🏾

/* Let's assume we were building a news website */





/* custom UTILITY CLASSES here for layout or just use [ TAILWIND CSS ] */

.display-block {
display: block;
}

.display-flex {
display: box;
display: -ms-flex;
display: -moz-flex;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}

.flex-row-no-wrap,
.flex-column-no-wrap {
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
}

.flex-flexible {
flex: auto; /* Override back to default */
-webkit-flex: auto;
-moz-flex: auto;
-ms-flex: auto;
}

.flex-fixed-\[\+350\] {
flex-basis: 21.875rem;
}

.flex-fixed-collaspable {
flex-shrink: 0;
-webkit-flex-shrink: 0;
min-width: 0;
}

.flex-fluid {
flex-basis: auto; /* Override back to default */
flex: 1;
-webkit-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
}

/* SEMANTIC CLASSES + FUNCTIONAL ATTRIBUTE SELECTORS for colors & font-sizes */

/* The form here is a `message` with clear semantics,
the function here is the context or use case */

.message {
font-size: 0.032em;
font-style: normal;
font-weight: normal;
text-transform: lowercase;
}

/* Seperate the function from form - news headline messages */
.news-headline .message,
[role="marquee"] .message {
font-size: 0.0625em;
font-weight: 700;
text-transform: uppercase;
margin-top: 0.021rem;
margin-bottom: 0.021rem;
color: white;
}

/* Seperate the function from form- citation messages */
.citation .message,
[role="note"] .message {
font-style: italic;
text-transform: capitalize;
text-decoration: underline;
margin: auto;
color: inherit;
}

/* Seperate the function from form - notification messages */
.breaking-news.notification .message,
.notification .message,
[role="alert"] .message {
text-transform: none;
font-weight: 400;
}

/* Seperate the function from form - comment messages */
.comments .message {
border-left: 2px solid brown;
text-indent: 0.0055em;
line-height: normal;
}

a[target="_blank"] {
clear: both;
}

a[href][target="_blank"][rel="external"]:before {
content: "\f400";
display: inline-block;
float: left;
}

[role="alert"][data-variant="success"] .message {
color: green;
}

[role="alert"][data-variant="failure"] .message {
color: red;
}

I do earnestly think that the future of CSS authoring on the web will require a healthy balance of Semantic and Utility classes which are arranged within CSS source files and folders by feature and not by type. This will reduce the occurrence of duplicated code and dead code and make it easier to find semantic styles when you need to change/modify them.

Beware the issue of margin-collapse 😟

Margin collapse is an occurrence that makes it difficult to have 2 block-level elements with margins sit side-by-side in such a way that the distance from each other is exactly the sum of the margins set on each of them. For more info on this see this article. It’s also highlighted in the CSS 2.1 specifications too.

I hope that you learnt a whole lot from these tips. Finally, i am not in any way saying don’t use CSS frameworks and component libraries. I encourage you to use them but keep these tips in mind as you do so.

Have fun writing great and scalable CSS! 👍🏾

--

--

Ifeora Okechukwu

I like puzzles, mel-phleg, software engineer. Very involved in building useful web applications of now and the future.