Margin considered harmful
mxstbr.com
mxstbr.com
The reality is that frontend is messy, browsers are still inconsistent with respect to how they treat CSS, and creating new layers of abstraction to handle trivial considerations like adding space on a display are just a way of denying that reality by fixing something that isn't broken.
Creating new, completely useless components to "bring us closer to how designers think" completely flies in the face of separating view from the program layer. And in my experience, "designers think" everyone has a 27" Thunderbolt display, or now increasingly a Pro Display XDR, so I wouldn't really put too much stock in what they think. If you disagree, I would propose spacer.gif as a much more succinct and widely accepted alternative to all the harm being done here.
Insert Game of Thrones too dark meme here...
Are they? CSS seems to be rock-solid and consistent now. The days of IE6 randomly breaking the box model are far behind us.
- margin-top inconsistent between Firefox and Chrome in some cases, usually near the start of a container element.
- specifying percentage height for absolutely positioned elements inconsistent in some cases between Firefox and Chrome (bugged in Chrome)
- SVG elements or CSS backgrounds are handled inconsistently by Chrome, Firefox and Safari. Which can depending on the use case can lead to issues like exceeding max-width, failure to apply aspect ratio, or in the case of Safari full page reflows every frame where an SVG is animated.
- Chrome incorrectly renders any scrollable element with backface-visibility: hidden. Requires non-scrollable nesting div.
- Everyone has a different way of specifying custom scrollbars. On some platforms you need to leave room in any scrolling design for a scrollbar which will reliably be an unpredictable width. Chrome and Firefox still have inconsistent non-standardized css proprties. Or (some) Apple users get the self-hiding scrollbar. In practice this needs to be detected and fixed after the fact with Javascript.
- position: sticky unusably broken in Safari
I'm sure there are others, but my point is the browsers are quirky and better to just face it and get over it.
Similar to Facebook's 2G Tuesdays, I'd love to see what designers would come up with if you made them spend one day a week using a 13" 1366x768 200 nit TN screen.
Why do people stick to this concept like a religion as if it's the only right way that has no tradeoffs though?
If you want a heavily designed look to your website or UI, at some point you're going to have to add annotations to your data for purely presentational reasons either because of limitations with CSS (e.g. there's no way to style the first word of a paragraph with only CSS) or because there's no semantic reason one piece of data should look different from another other than it "looks nicer that way" (e.g. adding a line break in the middle of a heading because you want a two word brand name to appear at the start of a line).
You might be able to avoid presentation-only HTML markup by preprocessing the HTML, modifying the HTML with JavaScript, using some CSS hack, excessive annotations that might never be used (like wrapping all brand names in tags) etc. but is it worth if for this purity of separating data from styling? It has tradeoffs.
Can you give an example to illustrate this approach?
This is overengineering, something that should be handled in a view with a CSS class, instead of an abstraction that ends up translating back into a CSS class after the overhead of parsing the abstraction.
I think in the case of your original comment to me, you're arguing something different. You want to bold the first word of an article or apply a line break after the first two words. For a use like that, there's nothing you can do other than to put some kind of view-related functionality in your code, and I agree. But handling that in code isn't fixing something for the sake of fixing it, because there's no way that it could be handled natively by the view in the context of whatever data you pipe into it dynamically.
A CSS class that provides margins to items in a div can be handled natively by the view and should.
However using a in your brand name would likely be a better way to handle this, and makes actual semantic sense in your html. It's also coincidently more robust to changes in your layouts.
Purity for it's own sake is problematic, and definitely sometimes there won't be a good semantic approach. But thinking through cases a little deeper and trying to represent things better semantically can pay off big dividends by increasing the robustness of your solutions.
I don't see how Brand Name is any more semantic than <div class="no-line-breaks"> Brand Name </div> though. Either way, I can't see how there's a general solution for that example.
- Maybe for another heading, the break in the middle of the brand name will look better because otherwise the previous line would contain only a single word.
- Or the approach works okay for short brand names but not very long ones (e.g. Department for Work and Pensions).
- Or maybe for one heading we want a break only because otherwise the brand name is going to overlap with the eyes of a person in a background photo.
CSS doesn't even have anything built in to do nice looking justified paragraphs yet (like LaTeX has), let alone simple stuff like dealing with text widows and text orphans.
You could conceivably come up with a general algorithm that encodes some of the design heuristics above but that's in the realms of automating graphic design and there's always going to be exceptions.
My point is, there's no clean separation between content/data and styling if you want your data presented in a very specific way. For heavily designed pages, this is underscored by it always being unavoidable that you'll end up modify the text content because you want it to look more presentable e.g. changing a heading so it to fits in a tight space or line breaks in a pleasing way.
I can't see how that's ever going to happen. You're going to tell Apple or a professional graphic designer to not care about small details in the way their pages are presented? Raw data and semantics isn't the single most important thing in all cases.
This is a honourable vision, sure, but also a completely unrealistic one.
HTML is the view layer. The "program" layer is JS manipulating JSON (ultimately fetched from a DB).
Separating HTML and CSS made sense when content lived in HTML, and styling lived in CSS. Content hasn't lived in HTML in a long time— it lives in a DB now. (Maybe markdown if you're making a static site.) Separating HTML from CSS is separating view layer from view layer— just an unnecessary and expensive indirection
The article just argues that spacing between sibling components should be specified by the container rather than by the individual components.
This makes sense in some contexts where you have a uniform spacing between components. In other contexts, like an article with headers and paragraphs, it doesn't make sense. But you probably wouldn't use React for that in the first place.
In CSS you can do both and the article is not pro or con CSS in any sense.
I wish this were not the case, but having researched the front page HTML of the 10 top trafficked websites during a new framework selection for a client, this is exactly how things are done.
If you want a better way for a project that you will have to maintain for a longer time that should be more flexible, I would personally suggest something like BEM[0] + a few best practices (like the one suggested in the post). This will already get you a long way.
[0]: http://getbem.com/
Styling an important or often reused component? You best find a better way.
Adding a tagline to a marketing site or temporary banner? Yea, do whatever you want to get the job done. Just don't let your whole site look like that or you'll have angry teammates.
One time I asked a musician with a degree if I should go back to school for music. I was wondering if he thought it was worth the money and time. He said:
"You can go learn all this stuff and you'll know everything about every song you hear. You'll understand all the underlying structures and how to write it out on paper. And music will become an entirely different thing to you. But guess what? You'll be broke, because that's not what people pay musicians for. They pay musicians to sound good and sell alcohol. All that other stuff only matters to people trying to pass their final."
That's always been the industry standard though. It's a wonder the web works at all.
<MyComponent></MyComponent>
<div class="spacer"></div>
<MyComponent></MyComponent>
They are describing using layout components to define how children should be oriented and spaced.The article's example:
<Stack space={3}>
<Item />
<Item />
<Item />
</Stack>
Would be basically equivalent to this: <style>
.layout-space-3 {
> * {
margin: 6px; // Or whatever spacing value to use
}
}
</style>
<div class="layout-space-3">
<Item />
<Item />
<Item />
</div>Stack { display: grid; gap: 6px; }
It makes a lot of sense. Margin should be a property of the enclosing container, not the items in it.
[1]: https://doc.qt.io/qt-5/qml-qtquick-column.html#spacing-prop
Truthfully, it's increasingly my opinion that design patterns (and by extension, anti-patterns) only exist in the context of focused, internally consistent systems architected by a small group of individuals and not a committee. Rails, especially early on, is a great example. Actually -- a huge chunk of programming languages are built this way. The web, however, is designed by committee.
With this framing, "avoiding margins" works for the way Max builds design systems -- and from what I know, he's very talented and the team(s) that work with him should adhere to the systems he constructs. I'd love to see an implementation of an entire framework / design system that avoids margins -- something like Tailwind CSS. I do not, however, really believe this is broadly applicable advice. Most people are just trying to ship something that looks nice, and margins can be overwritten and adjusted easily.
NOTE: I have components I've written that have margins I have to overwrite nearly universally, but can't remove because of other UIs relying on the margin behavior. So I understand where the argument comes from. It's just -- I'd have a hard time if anybody tried to pick a bone with a software engineer on the team about the use of a margin if the initial intention was good / legitimate.
Using margin will guarantee it'll have at least 20px and likely only 20px top and bottom with the way margin collapsing works with CSS. Padding or spacers would just add potentially more space than the minimum design requirement.
It turned out to be a mistake. It added a component (with JSX file, an index.js and a folder, and imports and uses in the consuming file. 10s of lines of code across multiple files and folders. After I had made it, I kept seeing the layout pattern it encoded in other parts of my app. I’d reuse the component, break it, refactor it to cover multiple uses, which sometime broke the original. I’d give up and make a new component that was slightly different. In the end I’d have several of these things, all with long names that were hard to interpret and harder to use.
And I was the only coder on that project. Imagine if you had several people doing this?
This kinda thing is the same as the problems people complain about when working with large CSS codebases.
Doing it in CSS is marginally easier, in my view. You probably have fewer files to maintain and fewer lines of code to work with.
Ultimately, I think the problem is in neither the CSS or JS. It’s that layout is really hard. It’s as subtle as language. We strive for regularity and consistency, but if we stick it it strictly, we hamper our ability to communicate.
If responsiveness is what you're after, you can easily make your spacers adaptable to different screen sizes.
Of course. But why? You're just adding complexity at that point. It's the same problem with frameworks like Bootstrap that use complex combinations of class names for styling. Now instead of having a semantic BEM style class name on the component that I can look up in a stylesheet and modify, I have to memorize an entire framework of esoteric 'col-md-whatever' class names and know how/where/when to apply them. It adds a massive cognitive load beyond just using CSS. In the instance of a custom spacer component described above, I'm now also on the hook for documenting and testing a component that is entirely visual, rather than just writing a couple lines of CSS.
There's no reason it would add unnecessary DOM nodes. You can add styling on top of any component if needed (styled-components is one way but not the only one). Having a `columnGap="small"` is perfectly acceptable for me (`columnGap={3}` too if 3 refers to some scaling, `columnGap="10px"` is bad).
It's similar to the Tailwind approach: have ready-made pieces of UI you can compose. But I also get why you wouldn't like it, and I think any of these approaches work fine enough if you they're used diligently
For example, it feels nice to define margin or padding on a <p> element, but what does that mean - distance between two paragraphs? Between paragraph and heading? Between paragraph and bulleted list? Using spacers seems like the only way out of this madness.
Can you explain what spacers are and why they'd be better for this example? Why not use CSS rules that say e.g. "use 1em spacing between p tags", "adding 2em spacing between h1 tags and p tags"?
p+p{margin-top:1em}
h1+p{margin-top:2em}
This says more or less directly "1em between p and p, 2em between h1 and p". The values can be changed independently, with no need for collapsing margins and so on. It's a bit unusual though.What’s befuddling to me about these threads is that they keep trying to make an absolute case. It’s true, I use margin less now that I have grid gap, but there’s still a place for it.
And protip: Whenever you have a CSS problem, think: "Oh, I'll just use negative margins!"
I don't want to be rude but you are completely misunderstanding what CSS and HTML are supposed to do.
1) The <p> tag denotes that the content in the element is a paragraph in the document.
2) The styling is just tell the client (which is typically a browser) how the element is supposed to be styled. It isn't supposed to mean anything in relation to other elements and it isn't supposed to convey meaning.
Generally wherever possible you should try to separate the markup and its styling. You can't always do that, but you should try to.
The only other case where I can imagine an element being improperly spaced is when its parent’s padding is messed up.
If you've heard "separate content from presentation" all your career, you might autonomically hear it as "separate HTML from CSS". I used to.
In a React app world, your content hasn't lived in HTML for a long time. It lives in your DB, and comes to you through JSON.
Your React codebase exists to style that JSON. That includes "HTML" (JSX), CSS, and whatever else you've got in there.
Since your HTML is for styling, and your CSS if for styling, why separate them? If you do, you're not separating content from presentation, you're separating presentation from presentation, and that's just wasteful.
Personally, I'd prefer to use inline styles for everything, though of course will use CSS for :hover of @media queries where I have to.
It's not anything new really, everybody has experienced pain to try to reuse a component but it's badly encapsulated so it's painful to fit in a new context. It's always worth making it clear and reiterating basic principle under different lights, but this "considered harmful" like you discovered a new law of physics always feels pretentious to me
It's hard to define margin without any relation to neighboring elements, it's easy for padding and border. It seems logical that a reusable component shouldn't make assumptions about its neighbors, therefore avoid margins on its boundaries.
It's just a tool to be used. Sure it can be harmful if you don't understand it.
https://twitter.com/wongmjane/status/1242370883320049664?s=2...
I remember @jpochtar and @gablg1 had a similar take in "Technical lessons from building a compiler startup for 3 years". Under "Don’t trust standards", they wrote "We had no issue w/ using invisible spacer divs instead of the more “semantic” margins or paddings."
https://medium.com/@gabriel_20625/technical-lessons-from-bui...
Google and screen readers aren't going to be confused by you putting a < div class="spacer > < / div > tag in the middle of some < h1 > and < p > tags on a page.
I think coders just need to accept that it's a failed experiment to keep all styling data out of HTML. We've been trying for decades now and it's not practical, especially when you're adding a high level of polish to the presentation of your page while dealing with current web standards.
Just use the correct tool for the given task.
so instead of:
<div class="link"/>
<div class="spacer"/>
<div class="link"/>
<div class="spacer"/>
<div class="link"/>
We should be doing:
<div class="link space-right"/>
<div class="link space-right"/>
<div class="link"/>
This allows the reusable styles to be reused and the spacing to be one off.
P.S. since I'm more of a react/styled-components kind of guy, this is how I would look to do it there
const Link = styled.div`
//... styles
` const LinkWithSpacer = styled(Link)`
//... margin
`
<LinkWithSpacer />
<LinkWithSpacer />
<Link />
Once you get to the size of an app which requires a dashboard, and many specific distances, some just a tiny bit smaller or bigger, this gets unwieldy very fast. You end up coding around the spacer component or not using it entirely for some situations which makes the whole approach kinda useless.
The approach I take is the "pass your own classname"-approach. Im sure somebody has found a fancy name for it.
Having a componenent A:
const A = ({ className }) => (
<div className={`${COMPONENT_CLASSNAME} ${className}`}>
Hello, World!
</div>
)
You can then just pass your own classname from the parent into the child component. If your component A does not define any margins, it should be easy to define those and not break anything. As simple as that. I find that to be way more flexible than a spacer component, because most of the time, spacing depends heavily on context. The spacing component therefore either becomes too complex and unwieldy or does not get used at all.The approach from above does have a drawback: if you dont pass a className to your component, the resulting class attribute will contain "COMPONENT_CLASSNAME undefined". I have not found that to ever be a problem, although if you really want to, you can also write the template string as follows to mitigate this:
`${COMPONENT_CLASSNAME} ${!!className ? className : ""}`
But I do find that to be unnecessarily verbose.In fact, using margin correctly can prevent an explosion of exceptions on the relative positioning of your components. It also brings you more in line with how a designer thinks.
Consider this: one of the things a designer takes into consideration is composition and whitespace. In effect, this means that distances between components might change, depending on what their layout is, and what components are situated around it.
When you have a wrapper component, you can set some sensible spacing defaults for its children. But some components visually need more breathing room, while others need less. This depends on the design of the component. It also depends on what components comes before or after, so a simple padding won't suffice. Two visually heavy components need more space in between as compared to two that are visually airy. You'll eventually end up with a long list of + selectors to precisely tune every single combination. This quickly gets out of hand. With or without a wrapper element, those exceptions must be applied somewhere.
However, when using margins, you avoid all that. Since margins collapse, you can be assured that any combination of components has the minimum amount of whitespace between them, determined by who needs the most. Now this isn't perfect, but it gets pretty close; any component that needs a bit more breathing room can force a larger distance. This way, you might still have one or two combination exceptions, but these will be rare.
So, looking at it like this, margins _are_ a property of the component itself: it's the equivalent of a guy stretching his arms out and saying "don't come too close, I need my space!".
<div class="margin-10">..</div>
<div class="margin-20">..</div>
<div class="margin-30">..</div>
is MUCH cleaner than: <div space={3}>..</div>
<div space={4}>..</div>
The difference being, first of all, I don't need to dig into the source to see what `space` does. Next, appearance layer is tied to CSS and the JS takes care of the rest of the structure of the component.That said, I almost didn't read the post simply because "Considered Harmful" is the most obnoxious title format I know of.
I like the colorful history of this phrase, and how it usefully marks the following argument as an unbridled assault against purportedly common wisdom, so we can treat it with the huffy disdain with which we should normally treat such cheeky endeavors.
> Facebook has quietly marked Considered Harmful articles problematic, and that's a good thing.
> When I was a child, my father often took me fishing as a way for us to get out of the house. I remember the dappled early Sunday sunlight playing across the dashboard as we drove up to the lake in our 1998 Subaru Forester, a car whose longevity is likely to be greater than mine. Subaru, as it turns out, tapped into a fundamental part of the American psyche that had, at the time, been left completely untouched out of sheer unawareness.
Feels like we could have saved a lot of time if we'd considered at the outset that maybe Knuth, an actual genius who spent years on this problem, had some pretty good ideas?
Whenever possible, I try to use the sibling selector for this –
.header + .nav { margin: // }
This way, both header and nav elements remain re-usable and the margin only comes into play if they're used in a specific layout, and it is defined in its parent.
Margins need to come from the assembling component, rather than the detail item. Spacing requires awareness of multiple elements, which you usually don't have internally.