It’s a mistake to think that you must use some form of a framework to style web pages.
That’s not to say that I don’t appreciate tools like Bootstrap and Materialize. They are powerful because they allow developers to rapidly develop web applications that are production ready. If you just want to write code, a framework can help you write an app that doesn’t look like total garbage while touching little to no dreaded CSS. I wish Bootstrap was around back when I got started learning web development.
I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail. — Abraham Maslow
When a tool is that good, it’s easy to fall into the trap viewing said tool as a panacea. Even worse is when one believes they can’t do good work without it. Sometimes a tool really does become a necessity, but this is certainly not the case with CSS.
The troubled history of CSS comes mostly from the fact that it’s so simple for development newbies to learn through brute force, and that it’s too easy to structure poorly in a project. Anyone starting out in web development can examine web pages in their browser and see how things are styled and then copy/paste those styles into their own project. When a global style property is preventing a style on a specific element from working, the newbie developer does a Google search for “how to override a CSS style” and learns of the evil !important flag.
At first, it’s easy enough to just build colossal stylesheets with several properties overriding each other and !important declared where some properties are in conflict. This is exactly what the Cascading Style Sheet was designed to do. It’s a powerful thing, but common practices around them become a headache for developers and designers alike, which is why it’s common to resort to framework code.
In the past, there have been more legitimate reasons for depending on CSS frameworks. Properties such as flex and grid did not exist yet, and browsers had poor support for CSS transitions and pseudo classes. The icon fonts available through these frameworks reduced the headache around implementing basic icons, especially before SVG sprites became well supported.
At this point, CSS frameworks solve fewer everyday problems than they used to, and although I still maintain that they are great for prototyping and for building tools where custom design isn’t necessary, they may hinder one’s understanding of how to write effective CSS. For the seasoned developer, a framework feature as simple as a dropdown menu might not function as they’d like, thus forcing them to override someone else’s code.
By slamming my head against writing framework-free CSS multiple times, I developed some patterns around writing CSS that keep application design comprehensible without giving up simplicity.
My beliefs around writing effective CSS
- Replaceability is better than reusability. While some elements in your application will naturally share style properties, many won’t, and the chances that you will share universal styles across your applications is relatively slim. Generalizing everything more often than not mucks up your stylesheets, and your generalized class names will make your markup hard to understand. It’s much better to write very specific styles so you can easily change and rewrite them without worrying about messing up other styles.
- Separate your concerns. The key example of this idea in CSS is the difference between layout (size and position of larger components) and styling (colors, sizes of smaller components). Separating these ideas not only makes your CSS easier to understand, but it relates to my first point in that the principle of least knowledge allows parts of your stylesheets to be significantly changed with less chance of catastrophic effects on other parts of it. There is an example of this concept in action later in this article.
- Do more with less. If you’re thinking of using the words “container” or “wrapper” in a class name, consider how you might be able to avoid using such a parent element in the first place. I’ve found that while one’s first instinct when dealing with a layout problem might be to just wrap a group of elements, it’s actually unnecessary ~95% of the time. Modern CSS is so feature rich that you can usually find a way to do what you want without superfluous elements.
- Relative units are more trouble than they’re worth; use them as little as possible and instead just go with pixels. Why not the EMs or REMs that everyone else is using? Well, a pixel isn’t really a pixel anyway — they are relative to the browser’s reference pixel. When a user zooms in on a page in their browser, fonts are not resized independently of their surrounding elements, and all browsers have had that behavior for over a decade. Not once have I ever received complaints from users about issues involving my use of pixels.
Addendum: This blog post does a better job than I could in explaining why pixels are unjustly maligned, as well as the pitfalls of using EMs/REMs.
Let’s build a web page without a framework using better CSS patterns
Let’s pretend we’re going to build a basic web page for a blog post.
The first step we’ll take is to work out the basic grid units. When sizing the components of a page, I base them on a layout of 12 columns and 11 gutters (horizontal space between components),which represents the maximum width at which our main content will be displayed, similar to how newsprint is laid out. I also use different sizes for rows (vertical spacing between components) and page margins. Here are the sizes that I typically go with:
Those numbers are mostly arbitrary, and your designer might have a different preference if you are working with one. It’s important to figure out your different screen widths so that you can set breakpoints where you can adjust your page layout to fit the device size.
Here are the screen widths I use with the units above:
As with the base units, those numbers can be arbitrary. I happened to pick those specific numbers because they are divisible by the base units I chose; that means the base units can take up the maximum amount of space in a given screen width, but no more. For example, the tablet width is the same as 8 columns and 7 gutters. If those screen widths don’t work for you, there’s no reason why you can’t pick completely different ones.
The screen widths are used to size your main content. As breakpoints, I add 15px(our margin size) to the left and right so that our main content never completely touches the edge of the screen.
Why go through all this trouble when Bootstrap’s grid does essentially the same thing?
Well, almost the same thing.
From a visual standpoint, there’s no reason why you can’t achieve similar results with the Bootstrap grid system. My preference for manually writing layout styles is to reduce dependencies, write less-complicated markup (not littered with wrappers and generic class names), and retain the greatest control possible. By writing my own layout system, I can make exceptions for certain cases without relying on “overrides”. When there are edge cases, exceptions can be easily made without hideous hacks upon CSS written by someone else.
Writing my own layout system allows me to use units that both the designer and myself can understand. Being able to read pixel widths from either a Sketch file and translating those widths directly to element widths is preferable to me.
Now that we’ve nailed down our basic units, we’ll get started on our basic blog post page. It’s a fairly standard holy grail style layout.
Let’s write out the markup and CSS while following some conventions:
- All component-level styling should use class name selectors.
- Page layout-styling should only use IDs. This means that unique entities on a page will also be given specific IDs that our CSS will use to define their sizing and positioning (and in some cases their visibility).
- Follow a naming convention. I prefer BEM. BEMIT is too lengthy for my taste, and the use of IDs and class names described above reduces the need for prefixes. In reality, any naming convention will do as long as you stick with one.
- Alphabetical order is better than non-alphabetical order.
- Avoid deeply nesting elements for the sake of convenience when positioning elements with CSS.
- No “master” stylesheets. Every CSS file is dedicated to a limited scope.
- Semantic HTML is a good thing.
Look what we’ve ended up with:
It’s not the prettiest thing in the world, but it’ll do for this example. In only a few hundred lines of CSS and HTML, we have workable blog page in front of us.
As you can see, I’ve kept layout and component styling separated from each other. ID selectors in app/styles/article.css only define positioning and visibility. This stylesheet for the page has no knowledge of the internals of the components being used.
The components of the page “snap” to each distinct screen width that we’ve defined. The breakpoints used to determine where this sizing happens includes our screen width variables as well as two margins (15px), one for each side of the screen, which prevents the content from touching the edge of the screen and leaves enough room for the scrollbar.
There is a potential problem, however…
What do you do when the children of a component need to be responsive?
It’s trickier to get right than it first seems. Components can certainly use their own breakpoints to react to screen width, but screen width alone is often not enough because the size of a parent container/element often matters.
Sadly, CSS is crippled in that there’s no way to query the size of a parent element. Such a feature would solve a lot of problems in page design. We might not always want a component to respond to screen width the same way on different pages, but we also don’t want the inner workings of a component to get tangled in the page layout styling. In my opinion.
To get around the problem of component-child responsiveness, I prefer to use Sass to give each component a standard set of mixins that the page layout can include where needed.
Say, for example, we want our body component to place the headline underneath the lead image on mobile screen sizes; how might we do this without the layout styles requiring specific knowledge of the child elements of the body component?
Let’s revise our blog page styling using the “SCSS” flavor of Sass:
You’ll notice that to change how the body component displays its children on mobile screens, I’ve created a Sass mixin called prose — sm, which defines how our prose element should be styled on small screens. That mixin can then be selectively included in #article__body media queries through our layout styling. This is how we are able to move the headline below the lead image at a certain breakpoint. Writing mixins for components with a standard naming convention (in this case <component name> — <screen size> allows us to define different component states and use them selectively in the page layout without entangling the layout and component styling. To a programmer, the mixins become your public API to each component.
Yes, we could certainly have achieved the same thing without Sass by making the assumption that we’ll always use the same media breakpoints throughout our site, and using media queries with those breakpoints directly in our component style sheets. It’s viable to do this and there’s nothing wrong with it.
But relying solely on your media queries can become a headache when you need your component to have unique behaviors on different pages at the same screen sizes. There are creative ways around this, but I consider the approach of using Sass mixins to be the most feasible because it not only allows component styling and behavior to be easily altered wherever and whenever we want, but it means we only have to write one set of media queries per page layout; there’s no need to define the same media query in various places, which can cause unexpected page resizing glitches if you’re not careful enough. A component can have an indefinite amount of states, and they have complete ownership of their internal layout at all times.
It’s these practices that I’ve described which have made designing webpages, a very unpleasant process, become a much more tolerable process for me. Although my approach involves more work in a few areas, it’s reduced the number of times I’ve been astonished that something isn’t working as expected.
Some of my beliefs, like the one around using only pixels, are totally relative (wink wink), but I hope you can take a few of my ideas and adapt them into your own process to the betterment of your productivity.
If you really don’t mind using percentages in your page layout, you might want to check out a new Sass tool from ProPublica that makes it easier to build grid layouts using percentage widths without a framework.