It looks like composition over inheritance has caught on as the better default in other languages, but in the CSS world people still cling to the cascade as a best practice for some reason.
It looks like composition over inheritance has caught on as the better default in other languages, but in the CSS world people still cling to the cascade as a best practice for some reason.
I find this falls into generally two camps, those who want to fight the browser and those who don’t.
Those that tend to hate the cascade tend to fight the browser a lot, whether they realize it or not. Generally speaking, they want things to work a certain way (IE everything in isolation) and prefer to think of styles isolated bits.
The second camp tends to not fight the browser and embrace the browsers methodology. They embrace the cascade because it’s easier than fighting it, but it requires a more sophisticated approach and seeing styles in a wholistic manner, not isolated purely into components (though may be organized in a way that co-located them with relevant components)
Both work, ultimately, and modern tooling and approaches allow both to exist, but I will say, the second group often has a better grasp on keeping project maintainability over time in my experience.
So, wouldn't that be the browser fighting them, then?
They want something specific, and the broswer forces a paradigm upon them that they don't want.
They are the humans and the browser (well, the styling language of the browser) is the tool that should accomondate them, not the other way around.
It really depends on how you view the browser, IE should browsers be more adaptive to certain paradigms or should it set a reasonable paradigm and enforce it? I don’t know that there is a 100% right answer in this case, though they have moved to create better hooks for some forms of isolation (e.g. layers, scoping) but they fundamentally haven’t walked away from the cascade aspect.
It’s converging the two paradigms, for sure, but as I said, I don’t think either is wholly incorrect or correct.
Now if you wanted my opinion on the whole thing, I think the cascade is a fundamental element to be leveraged not avoided, but that’s me.
I don’t think CSS is the perfect tool for all browser-based styling but it’s the tool that’s there and it’ll probably work a lot better if you use it the way it’s intended to be used. If you want a screwdriver instead of a hammer… you have options (don’t target a browser, propose an alternative to CSS, use something that compiles down to CSS).
If someone wants to hammer nails and they're given a blender, then the blender might not be defective, but it surely is not the right tool for the job, and it's imposed upon them.
Few people ever loved CSS. The majority always either hated it or learned to tolerate it. Most who do CSS today use a few different paradigms on top to make them tolerable like BEM, or use different transpilers to get a better language, or directly control styling from code, with CSS-in-JS libraries or like React does it.
>I don’t think CSS is the perfect tool for all browser-based styling but it’s the tool that’s there
Sure, I never denied its existance. Just its design.
Another anti pattern I have seen is the over use of media queries to force the browser to do certain things rather than embracing relative sizing constraints via intrinsic design and letting the flexbox and grid algorithms do most of the heavy lifting.
Here though I want to point out isolation is relative, as is the cascade. I think it’s important to leverage the cascade wherever you can but that doesn’t mean you are leveraging it from top to bottom per say, but it does mean thinking more wholistic about the context of styling
Those efforts don't just dissappear quickly, since that was the only way available that worked across all browsers for nearly a decade.
Do these two camps align with your two camps? Those who want control fight the cascade, others just fiddle with it until it looks good?
I don't mean to disparage either camp, because we need both approaches in the real world.
When you are a beginner your S is close to zero, as you gain experience you develop increasingly better compression to be able to grow your effective S, but it’s never infinite. There’s always some size program where you will devolve to strategy 2, and all programs tend to grow in complexity until they reach the point where nobody understands them, like some kind of complexity Peter principle.
That's probably in the eye of the beholder and a necessity. If you are all in on the cascades then you have to limit the depth of your page structure, otherwise it becomes impossible to predict how things will ultimately render or what the impact of a change at layer 3 will have on the rest of the page.
Technically speaking, isolation is perfectly possible with webcomponents.
In my opinion, this was a failure of communication from the beginning, with a huge divide between "I want my website to look like this because I drew it on paper with my designer friend" and "css is a cool new way of doing decent programmatic ux/ui.
Variables and/or utility classes mostly solve this so not sure how it's related.
> Since then we've got more tools to control cascade: layers, scopes, inherit/initial/revert/revert-layer/unset for every property, etc. But people still insist on inline styling. I get it, it's much simpler. But they will keep being unhappy until they learn cascade and stop fighting tools. Yes, learning takes effort but so does resistance.
Similar to inheritance-like features in other languages, I have learned it and find composition is the better default. It's too complex for too little benefit so I avoid it wherever possible.
I find maintainable CSS approaches are all about reducing the blast radius of style inheritance. So people are writing maintainable in sprite of cascading, not because cascading helps.
> specificity is hard to grasp and order-dependent resolution
Specificity is another foot-gun to avoid for me. It's learnable, yet easy to forget later. When you're tempted to rely on specificity there's usually a more readable way to do it that's not going to bite you later.
Do you doubt the developers behind Tailwind library understand specificity and the cascade? They clearly must understand it, use it where it helps, and avoid it where it doesn't.
I agree. This sounds like saying cascading helps in simple cases but when it gets more complex it doesn't help.
I don't think anyone really has a big problem writing maintainable CSS for a document. It works fine there. Most CSS methodologies (like BEM) and frameworks (like Bootstrap, Tailwind) are there to help write maintainable CSS code for complex non-document web designs, where a big part of that is taming cascading.
I think a lot of pushback against approaches like Tailwind and JS frameworks are from people that are mostly styling documents. Complex landing pages and UIs are where the real headaches are.
"Prefer composition over inheritance" was mentioned in an early page of the GoF book (the Design Patterns book) - in 1994.
There was a company that I worked at 10ish years ago with a SaaS built around Drupal. They had one monolithic css file that was 25000 lines. It was a nightmare. When I took over the front-end rebuild, went through it line by tedious line and broke it out into discrete components.
I think about that from time to time when building logic or other functionality. I'd much rather have a self-contained bit of code than a big thing that is so intertwined I'm terrified to touch anything.
There's some movement towards ::part as a proposal to grant some mixin behaviors (https://developer.mozilla.org/en-US/docs/Web/CSS/::part) but I've never messed with it, and it's applicable only to shadow DOM. But mixins haven't been a huge issue for me in even enterprise-scale styling that I've done.
Opinion me, everyone has their own opinions on this, use whatever CSS style works for you. This is not me saying that BEM is the best for everyone, just giving a perspective that as someone who tends to stick to vanilla CSS and who generally kind of hates working with technologies like Tailwind or CSS-in-JS, BEM-style vanilla CSS made CSS pretty pleasant for me to work with; I have a lot more appreciation for the language now than I used to.
So if you're annoyed by CSS but also get annoyed by pre-processors or think that Tailwind is just inline CSS under a different name[0], you still don't need to be bound to the cascade -- potentially look into BEM. No technology or compilation or dependencies, it's literally just a naming convention and style guide.
----
[0]: yes, I have used it extensively, please don't comment that if I used it more something would magically click, I already understand the points in its favor that you're going to comment and I've already heard the style/framework suggestions you're going to offer. It's fine if you like Tailwind, it's great if it helps you write CSS, you don't need to convince me.
I think cascading is just a bad default, and I think methodologies like BEM agrees with this by teaching you ways to write CSS in ways that stops cascading from getting in the way.
Cascading styles are fine for styling how basic document content is shown (e.g. h2, p, a, li etc. tags) but outside of this, you generally don't want the styles of parent elements leaking into the styles of child elements. Cascading/inheritance styles is a useful tool to have, but not as the default.
I'm not saying Tailwind is perfect, but it's closer to "prefer composition over inheritance", where you can sprinkle in some cascading/inheritance where it makes sense.
I'm again semi-inclined to agree, I just don't think I'd say it as forcefully; more that cascading styles tends to have a lot of downsides that people aren't familiar with and aren't taught.
My point isn't to badmouth Tailwind here; but debates about this sometimes boil down to "CSS purists" vs "Tailwind advocates" and my point is more -- nah, you don't have to like Tailwind to avoid the cascade. You can be a CSS purist and still avoid basic element selectors, your choice does not have to be either "do semantic styling targeting only semantic elements" or "jump on Tailwind and stick a bunch of styles inline."
I'm more sticking up for -- look, if you're someone who uses Tailwind, great, I don't have to tell you anything. You are already using a framework that (regardless of any other flaws it may or may not have) discourages you from using the cascade. But if you're someone who's in the position where you dislike CSS-in-JS or don't like using Tailwind, also great! I'm in that position too, I don't like Tailwind. But I still avoid cascade and basic element selectors and there are ways to basically eliminate most cascading styles from your codebase and eliminate most cascade-caused bugs even if you aren't going to use a pre-processor at all, and it's good to at least consider removing those cascading styles.
My only critique of Tailwind I would bring here is that sometimes I get the feeling that Tailwind advocates think that Tailwind invented this idea of component-based CSS, and it really didn't. But that's neither here nor there, and if someone is using Tailwind and it works for them, great. Life is way too short for me to argue with someone using a technology that they enjoy. Honestly, same with the cascade -- I think it can lead to long-term maintenance problems, but if you like it, fine.
However, if you're using CSS and hate it, and you also don't want to use Tailwind, then give BEM a try.
You could throw the same criticism at Tailwind -- Tailwind can still expose you to cascade issues, not all Tailwind classes are single-level selectors under the hood and not all Tailwind classes only target one property. At the end of the day this is all compiling down to raw CSS, so in neither situation have you actually eliminated the cascade. But with both BEM and Tailwind you are much less likely to see those situations, and when they do arise they are less likely to introduce long-term maintenance problems and are more likely to be easy to address/encapsulate. If you run into cascade bugs with Tailwind, it's probably something you fix in like one file, instead of needing to search through five.
BEM doesn't technically interact with anything, it's just a style of writing CSS. There's literally no technology behind it, it is just a naming convention. But in practice, using a naming convention mitigates or eliminates a large number of cascade issues.
----
In general though, I would actually suggest that you can kind of use anything if you're not planning on forking the component library. Most of these 3rd-party libraries you're not going to be restyling, so long-term maintenance and scalability isn't really a concern, you're never touching that code.
And BEM is just a naming convention, so if you're pulling in a React component and it has a hook for you to attach your own classes, then attach a BEM-style class, otherwise pass in the styling information into the props the way that most JS components want. I've used BEM with React components, with Material design, with Angular components, etc... I don't know, I haven't really run into issues. I've even worked on a codebase that was a mixture of BEM CSS, 3rd-party CSS, and Tailwind. It was fine, I didn't notice any major issues. Typically 3rd-party component dependencies are not a major source of cascade bugs in my experience, but maybe I've just been lucky. Most components I've seen lock down their styles to the point where it's kind of a pain to even try to override them with CSS, and the specificity required to do so forces you to effectively isolate those overrides anyway.
Whatever component framework you're using will have its own customization API, and in my experience that usually won't be handled through CSS. If it is handled through CSS, it will probably be handled by attaching classes, and then when you attach those classes you can use BEM. The major annoyance in my experience is that (for me) BEM often works better than whatever customization system that the 3rd-party components is using and I get frustrated that I'm mixing more straightforward CSS that's easier to debug and design in-browser in with whatever property-based thing that the components expose. But I don't usually think I run into many bugs?
What BEM helps with is dealing with cascade, code organization, naming, and debugging/search. With a 3rd-party component framework, most of that stuff is out of your control, so just use whatever the framework wants and then use BEM for the stuff that is in your control.
If you have to do some kind of CSS-based override of a 3rd-party component that isn't being handled through a component-specific API, wrap it in a BEM-style class for your actual component so that it's a one-time customization:
.Input__Username .some_component_depencency input {
/* This is not ideal, but (imo) you'll still effectively never really see cascade bugs from doing it this way, and specificity will rarely be a concern. */
}
One thing that is nice about BEM is that because your own CSS is being scoped to specific named components, it actually becomes a bit easier to have a lot of 1st-party CSS living alongside 3rd-party CSS and know that your CSS is not going to break the 3rd-party CSS. So I tend to worry a lot less about what other parts of the code/dependencies are using when I'm using BEM, because I more confident while writing BEM that I'm not introducing bugs into the other CSS.The cascade is so much not like inheritance that I wonder if you meant either the concept of selectors in general, or inherited properties?
I meant the concept that when you e.g. apply the style "color: blue;" to an element, all child elements get the same style unless you override it. I'm not saying it's identical to inheritance, but it's similar in that changes and additions at the top-level ripple down to lower levels, which I find causes most of the problems when writing maintainable code.
This is the first thing you've said so far that I would push back against. Neither BEM nor Tailwind removes this behavior that I'm aware of. I thought when talking about the cascade you meant generic styles on elements like "p", "ul", etc... getting applied across separate components, or specificity of child selectors, or something similar.
If you really dislike styles being applied to children in the DOM that don't override those styles, I don't think there is a way around that other than web-based components and shadow DOM with isolated styles. Or I guess use a bunch of style resets beforehand I guess? Neither Tailwind nor BEM gets rid of child inheritance of applied styles; you can use @layer I guess, but that doesn't get rid of that behavior either, it just allows you a bit more control over style order.
If you're using Tailwind and you write:
<div class="text-red-400">
<p>Some text</p>
</div>
that text will be red. If you're using BEM and you write: <div class="Container">
<p>Some text</p>
</div>
.Container { color: red; }
same deal.Yes, so I mean if you add "color: blue" to "p", it's now going to start interacting with any element that's a child of "p" (which will probably be on all pages on your website so hard to predict and check what will happen).
BEM and Tailwind don't get rid of the behaviour of the color being applied to child elements, but it at least forces you to isolates these kinds of style changes to the component level (vs sitewide) which is what improves maintainability.
The cascade[1] determines how rules from multiple sources are merged.
[0]: https://developer.mozilla.org/en-US/docs/Web/CSS/Inheritance [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade
I'm having trouble finding the original video, but Jacob Thorton (@fat) did an insightful talk on the whole affair called "Cascading Shit Show"
The cascade is a wonderful idea that has unfortunately not played out well in practice. Anyone who thinks it's just a skill issue is deluding themselves. In all my years, I've never seen CSS that (a) leverages the cascade, and (b) scales elegantly. It all breaks down past a certain point.
Tailwind has it's flaws, but it doesn't have the scaling problem.
Personally, my dream would be if inline styles supported the full CSS gamut of pseudo selectors and the child element selector. Then you'd have the admitted benefits of not needing to synchronize the two files, along with the benefit of not needing to relearn all of CSS.
Edit: it's funny, in a way, that all these developers complaining about how CSS "doesn't scale" are likely writing their Tailwind in an large scale application styled entirely via CSS. https://github.com/search?q=repo%3Amicrosoft%2Fvscode++langu...
As a trivial example, let's imagine we need to apply a border radius to an element. Without Tailwind, it looks like this:
1. I find the element I need to style
2. I look at which class it's using
3. I navigate to my CSS file
4. I scroll down until I find the selector
5. I type "border-r", then tab on the auto-complete to fill in "border-radius"
6. I type the colon character, then space
7. I think about what unit is appropriate - rems, ems, pixels, percentage
8. I think about what value is needed for the design
9. I also look around to see if this style is used elsewhere
10. If the style is used elsewhere, I think about whether I need to refactor
11. I type in the desired value
12. I type a semicolon to mark the end of the statement
13. I type cmd+s to save the css file
Here's the same example, with Tailwind: 1. I find the element I need to style
2. I type `round` and wait for the autocomplete to present my options
3. I use my arrow key to select which one I want
4. I type enter to add the desired tailwind class
5. I type cmd+s to save the html file
The Tailwind interaction path takes less than half of the concrete steps to complete. But it's even more dramatic than that, because several of the steps taken in the first example require enough thought that it breaks your workflow and takes you out of your flow state. Then you have to get back into that flow state to keep working. But this keeps happening, so you're constantly stopping and restarting. With Tailwind, I tend to find myself staying in that flow state, because as I demonstrated above, there's very little getting in my way.Normal CSS is usually worse than this too e.g. you hit save, and your edit doesn't change anything, so you have to use the web inspector to hunt down which class is overriding your style then weigh up options for how you're going to refactor while jumping between multiple files. It's exhausting when you're trying to focus on styling.
That said, I agree that this doesn't work for pseudo selectors, very unfortunately, and I wish it would.
1. I find the element I need to style
2. I add/edit the inline style={{}} attribute I have for it by typing `sty<TAB>{borrad<TAB>`
3. I add a value
VS: 1. I find the element I need to style
2. I add/edit the className attribute
3. I pull up the tailwind documentation to find how to type the CSS I already have memorized in their DSL (I know some Tailwind by heart, but wayyyyy more CSS)
4. I wait for it to load
5. I scroll down to find the version of the class name that I need
6. I go back to the editor and add the class.
Also a very common flow for me is to edit the CSS directly in the browser, it's a much faster devloop than the fastest live reload server. In that case it's far easier to just copy from the `changes` view into the CSS than go through and remap everything from CSS into Tailwind.