This is a personal collection of guidelines to architect a web project using CSS. This is a conclusion I've come to after trying to disband from TailwindCSS. This may be prone to change as I've only really divorced from TailwindCSS a month ago, but these are my discoveries.
This will probably be a living document depending on how lazy I am.
This also doesn't have a conclusion, because, um, yea.
Why?
This is a framework that is not targeted at smaller websites, or something like blogs, or fun websites. A lot of the rules that work there make life miserable when you have to add more shit.
This is for larger web applications in which you might have a lot of components, and you don't want to deal with Bullshit.
Layers
With the @layer at-rule, we can declare in which order which rules take precedence. I take inspiration of these layers and their usage from the ITCSS (Inverted Triangle Cascading Style Sheets) system.
In my projects, I now set up my base style sheet like the following:
@layer normalize, theme, global, component, util;
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap') layer(theme);
/* This is based off of the TailwindCSS preflight. */
@import url('./preflight.css') layer(normalize);
@layer theme {
:root {
color-scheme: light dark;
--color-page-background: #EEEEEE;
}
}
@layer global {
body {
font-family: Inter, sans-serif;
background-color: var(--color-page-background);
}
}
@layer component {
.card {
.card__header {}
.card--bordered {}
}
}
@layer util {
.__hidden {
display: none;
}
}
normalize
The normalize
layer is where I load a CSS reset or normalization using import.
In my own projects, I use a CSS reset based off of TailwindCSS's preflight.
I use this because I often have to stick to design guides and I try to use semantic HTML so that my interfaces stay accessible. I do not want to deal with the cascade and apply my own resets when I want to use a ul
element for something that has the purpose of a list but doesn't have the immediate presentation of how lists are displayed by browsers by default.
theme
@layer theme {
:root {
color-scheme: light dark;
--color-page-background: #EEEEEE;
}
}
I use the theme layer for two purposes:
I use it to set global configurations for things like variables. For example,
--color-page-background
in that previous example.I use it to load external libraries into.
The first purpose probably makes sense, but the second reason probably seems confusing. My mental model is that I'm loading the configuration of styling for external resources, like fonts, or how other applications are configured.
@import url('https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css') layer(theme);
In the future, I may expand this to include a library layer specifically for libraries like tom-select, and whether to include that before or after the normalize is something I'll decide as I come across edge cases.
global
@layer global {
body {
font-family: Inter, sans-serif;
background-color: var(--color-page-background);
}
}
I use the global
layer for 2 purposes:
I use it to configure incredibly broad elements that cascade throughout the whole application, such as configuring the font from the body element.
I use it to configure styles from other third party libraries, such as overriding the styles from tom-select.
As an aside, this is why I generally try to avoid element selectors.
component
@layer component {
.card {
.card__header {}
.card--bordered {}
}
}
This is pretty simple. Define components (blocks) using BEM in this layer.
If your block is meant to be used with a specific element, I usually put a comment with the intended element to be used, but it isn't something to be subscribed to.
/*ul*/.messages {}
util
@layer util {
.__hidden {
display: none;
}
}
This layer is for one-off utilities to apply to elements that should act as exceptions. I would recommend basically never using these, but they can be helpful if you want to apply a class really quickly to change its behaviour that don't really make sense to apply as states.
Use Semantic Elements
Use semantic elements for defining the structure and flow of document, such as ul and li.
The intention for how you present should come from things like classes, presentation shouldn't be inferred by the structure.
I avoid custom elements personally because it's a lot easier to use elements that are already made for describing the data that I'm holding rather than making custom elements that describe how it will be presented.
This makes it easier to make your interface accessible.
I use these add-ons to check quickly how the structure of my site is and how accessible it is:
Also, as an aside, this page is quite useful as well:
Use SCSS
SCSS still provides a lot of useful features such as mix-ins. Make mix-ins for things like breakpoints and so you DRY.
Making life harder just so you can use Vanilla Features? You can do that if you want, but I'd rather not.
@mixin desktop {
@media screen and (min-width: 768px) {
@content;
}
}
@layer component {
.something {
@include desktop {
/* apply shit to desktop here */
}
}
}