Use spacer components instead of CSS margins (2020)
mxstbr.comI do agree generally, but it's funny that the suggested solution (spacers) are basically back to the old 1px transparent gif trick from the 90s/early 00s.
Nothing wrong with the suggestion, but it's just ironic that we seem to be coming full circle again.
Next we'll probably have posts extolling <Table> components for layouts ;)
> I do agree generally, but it's funny that the suggested solution (spacers) are basically back to the old 1px transparent gif trick from the 90s/early 00s.
They aren't the same thing. Check the link in the article [1]. It is using regular CSS, and some of those components use more modern things like Grids.
They also aren't applied between components like old element-spacers. Component Spacers have much more in common with regular margin/padding than with 90s spacers.
[1] https://seek-oss.github.io/braid-design-system/components/St...
> it's funny that the suggested solution (spacers) are basically back to the old 1px transparent gif trick
Not really. The Stack component he uses as an example is actually just a thin abstraction over some reusable css. In plain html, it could look like
There are no "spacer gifs" anywhere, it's only that you're lifting the margin responsibility to the parent. I may be wrong, but I think the whole stack concept is borrowed from SwiftUI.<section class="stack stack--space-3"> <article class="item">...</article> <article class="item">...</article> <article class="item">...</article> </section>That said, I've recently started using actual spacer divs that are more similar to the spacer gifs of yore, you mentioned. So I could do things like:
Having been alive during the whole semantic web era, that feels very wrong to me. But it works like a charm to keep your code consistent with a design system which seems like a more noble endeavor than to write html that makes more sense to machines (which was, if I remember correctly, the objective of the semantic web).<section> <div class="vertical-spacer-2"/> <article class="item">...</article> <div class="vertical-spacer-1"/> <article class="item">...</article> <div class="vertical-spacer-1"/> <article class="item">...</article> <div class="vertical-spacer-2"/> </section>Stacks started in UIKit (UIStackView) and before that were cribbed from Android, IIRC.
"Stacks" are an ancient concept, you'd struggle to find a UI framework (other than HTML) that's missing an analogous layout tool. Java's Swing had BoxLayout, JavaFX had VBox, Qt has QVBoxLayout, etc.
Tendency towards semantic web is unfortunately dead. Now you have simple blogs rendered by shadow DOM components. Even with templates separation of template and code was ideal - now you have 'reusable components' with templates all over the place. Functional programming has its place, but I'm not sure if everywhere. (You can separate code from templates even with FP and React - but often it is not the case).
>Tendency towards semantic web is unfortunately dead.
Not dead, but under severe attack. We can and will fightback
Can you elaborate a bit why? I've don't think I've seen a time where most sites were generally semantic
Accessibility is the main reason, I believe
Search engines, screen readers, ancient browsers, future browsers, etc.
The important concept is that HTML tags should convey the meaning of its content, rather than merely being a target for CSS to define its appearance.
I don't think you know what Shadow DOM means. Shadow DOM is not just another virtual DOM. It is a very specific thing built into browsers used only for HTML Custom Elements.
It is not very popular. React, Vue, etc., do not use the Custom Element spec.
Concurrent with spacer.gif, there was also a `<spacer type="block">`[1] tag that was in use at the time.
Working at a boutique design shop back then, I remember trying endless combinations of the spacer tag along with spacer gifs to try to ship layouts that were at least semi (and hopefully mostly) consistent cross-browser.
Having lived through those web dev dark ages, I'm pretty baffled by the resurgence of 'markup as styling'.
Time is a flat circle, and we're just going to be rehashing the same ideas endlessly every five years until the heat death of the universe.
It's more like a spiral if you adjust the perspective.
We'll revisit old ideas from time to time after going the opposite extreme, but never in the same way.
Spacer components and spacer elements are not the same idea, though, far from it.
> but it's funny that the suggested solution (spacers) are basically back to the old 1px transparent gif trick from the 90s/early 00s.
I have to agree with others. It is not the same as 1px spacer hacks at all.
My company recently had a FE candidate supply a test using tables.
Serious question: What is inherently wrong with using Tables for organizing simple layouts? Is it frowned upon just because it is a "hacky" way of doing stuff?
I think it would be hard to make it responsive. Also, table is a semantic element. You should use it unless you're displaying tabular data.
I wonder how many millions of man hours could have been saved over the last twenty years if we could have added semantic="false" to our tables rather than redesigning everything.
You can make them responsive easy enough if you override all the table styling. I've done that before.
For me it's the semantic meaning, a table is tabular data.
However, I must say that using CSS grid is just like the old table-based design days.
https://twitter.com/Martin_Adams/status/1477214581449793538?...
Tables "mean" tabular data... grids of values with headings. The intent was that software could consume tabular data for processing without having to worry that it might be a "table for layout".
"table-like" layouts made using <div>s and CSS, including `display: table` are fine.
The original reason webmasters (remember them?) were discouraged from using tables for layout is that you quickly ended up with many layers of nested tables. The browsers at the time were not performant enough to handle it and the user experience suffered.
There’s still a place for tables in some use cases. But that use case is thin.
cries in email
That's very reductionist approach. Sure, margin is a sharp tool and you need to use it responsibly.
I avoid putting margins on "first"-level selector.
So instead of
.button {
margin-left: 16px
}
I do .parent > .button {
margin-left: 16px
}
Difference is that I can reuse '.button' elsewhere without modifications.Another point to consider is that margin is not the only CSS property that affects layout. Both grid, flexbox and 'position' properties should be used with same care. And approach I highlighted above is usually good enough.
For margin specifically there is also a "owl" selector that makes is a bit easier to manage
.parent > * + * {
margin-top: 16px; // OR
margin-left: 16px;
}I hadn't heard of the owl selector for selecting :not(:first-child). Thank you for teaching me something new!
The term was coined by Heydon Pickering [0]. He's great, also responsible for the useful site Inclusive Components [1] and one of the authors of Every Layout [2].
[0] https://alistapart.com/article/axiomatic-css-and-lobotomized...
Note that the owl selector is pretty slow when it comes to being processed by browsers. If you use it sparingly, it shouldn't be so bad, but leave it all over the place, and you'll see the impact
Ha, when I first learned about it I remade a pretty interesting navigation menu purely with :not :checked :focused :hover etc, so pure html and css, thinking it would be more performant then my previous JS version.
Its performance was not just measurably worse, it was obvious as soon as I opened the revision on my phone.
Lesson learned: it's important to think twice before using most pseudo selectors
> I remade a pretty interesting navigation menu purely with :not :checked :focused :hover etc, so pure html and css, thinking it would be more performant then my previous JS version.
That sounds so cool! Do you have a version somewhere that I can take a look at? I always like fiddling with clever CSS, even if it is too clever for its own sake, as you managed to find out
And keep in mind, that pseudo selectors are not created equal. Things like :checked or :focused usually don't have that much of a performance impact
Sadly no, I tried for a while until I finally threw in the towel at some point.
I think you're overestimating what I did though, the basic idea is this recreation.
https://codepen.io/wohlben/pen/gOGKejv
it was just a lot more elements in the navigation with subcategories etc, but basically the same methods
(remember to check the mobile view, as thats the one thats using most selectors. Though the desktop view is arguably better)
this recreation is probably very confusing though, as the only hint that there are more links hidden for the category are the missing corners - and there is nothing hinting you have to click the category names to show these hidden options. and i skimped on proper accessibility for this mockup, so the hidden options can't be selected without touch/mouse
The JS just emulates what opening the page would have done. It was SSR
> owl selector is pretty slow when it comes to being processed by browsers
I'm sure this hasn't been relevant for about 15 years. CSS rendering is _fast_.
As I mentioned above - one or two clauses like that won't hurt performance, but I have seen situations, when bad CSS (a lot of *'s, too long selectors, a lot of dead and unused clauses) actually had a measurable impact on websites' performance.
This especially comes into play, if you deal with a lot of nested HTML elements or when you modify the site's HTML a lot.
That sounds like a different problem from using the owl selector. That sounds like bad coding and app design.
Is there a place to see css benchmarks on modern browsers, like jsperf used to be for JS?
It seems like > *+*, *:not(first-child), *:nth-child(1+n) and *:last-child { ...revert... } are all equivalent. I would be curious to know which is most efficient.
How can one even test the performance of these selector? I've been have a few pages that feel slow, and have some exotic selectors, but I've no clue what tools to uses. And searches for CSS performance tell me lame things like: minimize and use a CDN. I have the XY problem.
Try adding 'browser' and/or 'layout' as keywords for your searches - that seems to surface stuff about the relevant sort of performance.
(this semi helpful advice brought to you by mst's complete lack of front-end chops meaning concrete help will have to come from somebody else ;)
You have the same understanding as the OP. The difference is that the OP is working on a component-based framework. In such a framework, it makes sense to declare the rule defining child margins *in* the parent component's code, not in the child's code.
But why do it with extra elements? The child should accept a class or possibly styles and the parent decides what those are.
Child directly styles itself with color, font, internal layout, etc
Parent positions child with flex box, grid, margin or absolute positioning. Spacer elements are not needed at all.
I think I see what you’re saying now. You are wondering why have dedicated layout components when you could “simply” get any component to define styles on its children?
You could do it that way too. However, when you reply on your component system to help assemble a UI, the components take on the encapsulation role you’d use rich selectors for in other situations. A layout component performs the same function as a utility css class, but is more idiomatic.
The article is about spacer elements. As in an empty div that takes up space so things next to it get moved into place. I'm arguing that isn't necessary, as css has plenty of tools for laying things out. I have no issue with components that focus on layout, that is fine. A div that uses say flex box to layout a set of children is quite common. I'm arguing that parent div does not need spacer elements, you can always accomplish the layout you need with standard css such as flexbox, grid, margin, etc.
Is there a easy way to do this with a utility framework like tailwind? It just makes it so tempting to throw the margin on a component.
This is one reason I'm not really into utility frameworks. They are able to deal with "appearance" part of the CSS just fine, but layout part is too rigid and inflexible.
You can use plain old CSS for layout purposes and mix it with utility classes for appearance.
You can do:
And then:<div class="actions"> <button class="h-10 px-6 font-semibold rounded-md bg-black text-white" type="submit"> Buy now </button> <button class="h-10 px-6 font-semibold rounded-md border border-gray-200 text-gray-900" type="button"> Add to bag </button> </div>
This should combine those techniques, but I haven't used it in practice. And I'm sure in this case you will be labeled as heretic by both people who don't use utility frameworks and those who do ;).actions { display: flex; justify-content: flex-end; align-items: center; } .actions > * + * { margin-left: 16px; // or better use var from tailwind to set sizes consistently. @apply: ml-5; // or do this https://tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply }Tailwind has gap-[0] for flex and grid, and space-[0] for the “owl selector” approach.
Needs (2020) in the title. Original discussion, 123 comments: https://news.ycombinator.com/item?id=22676442
Anyway, considered harmful articles considered harmful.
There is a time and place for margins. Being thoughtful with them is a better approach than "banning margin from all components."
Should I really ban the use of `margin-left: auto` when positioning my flexed elements? I don't think this article is actually advocating for that, but a novice reader may.
Being thoughtful is a better approach than "considered harmful" clickbait in any situation.
Regarding CSS margins, they are part of the box model even if certain stylesheets neglect them, and the only conflicts they can be involved in are easily solved (a rule for "container x" is more specific than a rule for "x" and can override margins, particularly with parent padding, in special cases) and they highlight genuine design defects: trying to use a component both with and without its "natural" margins (without adding appropriate classes or wrapping containers), trying to specify conflicting margins, trying to fit variable-sized content inside fixed-size containers by messing with the content's required margins, and so on.
A margin is contextual, so a reusable component shouldn't have them.
It's a shame that CSS doesn't allow the parent element to define how spacing around the child elements should work.
I thought you could do that with eg.
There might be things missing to achieve whatever result you desire, but CSS does allow a parent element to define how spacing (and any other styling) works for child elements.parent > * { ... }We can do this now with flexbox and grid. Spacer component from the article can be implemented with https://developer.mozilla.org/en-US/docs/Web/CSS/gap
> It's a shame that CSS doesn't allow the parent element to define how spacing around the child elements should work.
It really would be a shame if CSS didn’t have child selectors, and if they weren’t more specific than bare selectors.
You can do it with selectors, also with flexbox and the gap property.
Out of curiosity, why do you need to use "margin-left: auto" when working with flexbox items? What is it that you're trying to do, which can't be solved with "justify-content" and "align-items"?
Not OP but I had a scenario where I had a row with a variable number of items, left-aligned, and then the final item had to be right-aligned. "margin-left: auto" on the final item achieves that effect.
Still a useful article / discussion that belongs on HN IMO.
Clever, but this smells like a performance anti-pattern. Adopting this means you could, at worst, add 4 spacer divs per component. While most sites may never really feel a sting, for complex apps where reusability is a larger concern you've doubled to quadrupled the size of an already large DOM and you will be hit a death-by-one-thousand-cuts situation.
Now you've got more...
- html over the wire
- html to parse for first render
- DOM nodes to mount/unmount
- memory usage from excess DOM
- costly layouts due to extra nodes
- lighthouse complaints of an excessively large DOM
Otherwise a clean approach. Perhaps it could be solved at compile time or some other jsx -> CSS abstraction to maintain DX.
I saw a blog post a while back from a React developer who did this. They didn't use padding or margins, just `<div>` elements with an exact height and width. I fail to see any benefits of doing it that way...just learn and use CSS.
I'm not sure if that's what this post is suggesting, though.
Why do you need to add spacer divs? As long as the child components accept classes as props then the styling comes from the parent but is still applied on the children.
You can also apply margin via element and nth-child selectors, just as a note... Also - custom/shadow dom elements should allow classes - I think it doesn't work in angular yet - but should arrive in the next version(?). Also you could just use ::before ::after pseudo selectors instead of divs...
If you're worried about a few additional elements your DOM is already too big and you have bigger issues to worry about.
Can't you just use the spacing properties of the container? Like flexbox's space-around and space- between?
You can, however the lack of constraints on the spacing can make it difficult to match strict design systems. The dependence the spacing creates on the parent & child dimensions can lead to undesirable edge cases as well for dynamic/responsive content.
The opposite would be the intent and the goal. Align design system language WITH your spacer components ensuring strict adherence. Layout components work and they don't have performance issues.
I think we agree here. The argument I am making is that spacer components that introduce DOM bloat can negatively impact large apps, especially on low power devices.
A layout component does not have to introduce DOM bloat, it could apply layout directly via CSS to it's children. Layout components in this regard are incredibly helpful.
I also think repaints are probably more expensive given this solution.
More expensive with or without the spacer components?
I would think more expensive with spacers, you have more nodes to repaint, furthermore if you have an event that changes a particular spacer size - for example if you have contentnode spacernode contentnode and then change width of spacernode you will be repainting three nodes. Window resizing becomes more expensive seems to me.
Probably more expensive with spacers? I'd like to see some benchmarks though, I'm doing little more than guessing.
I think "spacer components" is a bit confusing and not really the message someone should take away from this. I think it's better to say something like "Make parent components responsible for managing spacing around their children", not as catchy I admit.
I think the main bugbear of margins that the article doesn't mention is margin collapse: I personally don't find the rules super intuitive and I would rather just be explicit with spacing than have the browser collapse things together!
Agree, collapsing margins are counterintuitive, and only applied to top and bottom margin. It’s really surprising how many people don’t know it’s a thing and fight the box model. If you think you’re constantly fighting layout, try to read the MDN docs [1] with a fresh eye. I was guilty to think I needn’t learn CSS, and reading the MDN really help me.
What's really surprising is how many people do know that margin collapse is a thing, but haven't fully understood all of the implications, so they still get mystified by some unintended consequence.
I don't think that "go read all the MDN docs" is a very useful suggestion. More useful is something like the following, which I don't think you will find anywhere in MDN:
When an element's top or bottom margin collapses with its parent, this may affect the position of the parent, because the minimum of the child and parent's margin will be used to position the parent. (MDN says the child element's margin will fall "outside the parent", but it is not clear that it will be used during layout to place the parent.)
The bigger problem is that CSS is just so complicated and arbitrary. Consider the rule for collapsing margins with parents, from MDN:
> If there is no border, padding, inline part, block formatting context created, or clearance to separate the margin-top of a block from the margin-top of one or more of its descendant blocks; or no border, padding, inline content, height, or min-height to separate the margin-bottom of a block from the margin-bottom of one or more of its descendant blocks, then those margins collapse.
They list five different factors that prevent collapse, one of which ("block formatting context created") is a link to a page that defines it in terms of 15 different constructs or situations in CSS.
On top of that, it appears that the MDN text is in error, because it lists intervening "height" as something that prevents bottom margin collapse, but not top margin collapse (when in fact it does).
This is according to me the real issue. Besides this unintuitive collapsability margins, there is only one difference between margin and padding: the edges/border of the background color.
It's the unintuitive collapsability that messes up margins.
I almost feel it should be written out of the spec, because a lot of people still don't understand it, designers and developers. And design tools like Sketch or Figma will never implement it in their tools.
If you don’t want the margins to collapse, set the container to display: flex.
One way around this is to only define margins at the end of the container.
Bottom margins can still collapse with those of a parent container.
Margin is generally more desirable than padding imo. Component/element knows the content it contains and uses margins to require a minimum amount of spacing. It avoids the need to make decisions on the spacing between every combination of components.
Padding only works if you know everywhere your component might be used or don't mind remaking spacing decisions every time you use it. In my experience minimizing code needed for each use of a component leads to more coherently styled interfaces, particularly on larger or faster moving teams.
The author is arguing exactly that: remake spacing decisions every time you use a component, because that’s what many designers (most?) do. This is also true in my experience.
There are several types of designers, and even on the web itself, several different things you might be designing. Designing a text medium (eg. a blog) should have more focus on semantic tagging compared to a SPA where you probably want an entirely different approach (and where talk of "components" makes more sense).
I like TeX's box model over CSS which allows for easier "smart" spacing (look up hglue, vglue, hskip, vskip, and eg. care for orphans when typesetting etc.) and smarter column alignment (trivial alignment on decimal point in tables).
While I'd never call myself a designer, I've done plenty of TeX (print and PDF) publications that people have commended for usability and appearance.
Without taking side to which is superior.
Typesetting something to a beforehand known format is radically different problem to a beforehand unknown format.
There is no need to take sides: there are things that are easier in CSS for sure, and I only highlighted a small number of things which are very nice in TeX box model that are somewhat similar to margins in CSS (non-explicit behaviour to help with smarter laying out of text). TeX has a bunch of problems for electronic documents too (box model it uses means that each individual glyph gets positioned as a separate box, which can result in unsearchable PDFs, for instance).
But TeX typesets to a beforehand known format (I assume you mean target document size) as much as HTML+CSS does (a bitmap canvas of <width> x infinity size): it's not optimized to reflow stuff live, but you can definitely change the page size before every run with a single command, and rendering should (mostly) reflow properly (this would be equivalent to resizing the window, or having differently sized/fine target like a cell phone).
It does not solve the problem of multiple page sizes any better than HTML+CSS, it just provides some nicer and more obvious tools for aligning stuff on the page/screen that could be more easily used to achieve such support.
I mostly meant that the rendering problems are radically different.
Typesetting is a rendering a static copy a priori. You can expend a lot more time and resources rendering and reflowing the document. Rendering HTML+CSS, which can be dynamic, is a radically different problem.
I wouldn't be surprised if these posed very different constraints on the possibilities of the layout model
What I am saying is that that it is not a radically different problem. They are radically different implementations focusing on different goals, but the problem is pretty much the same.
TeX's box model is in no way less optimizable than the HTML+CSS (if anything, it's the opposite due to it being much simpler). It has had a stable algorithm and implementation since 1983, and that implementation was never optimized for reflow like HTML + CSS has. TeX itself is also Turing-complete, which makes processing more expensive by definition, and that's mostly the reason for the slow performance, rather than the box model — and it is from 1983, after all. For some things, the reflow model is even used with TeX today (eg. table of contents generation usually requires at least two passes to "reflow" the output).
Håkon Wium Lie, the author of CSS, and fellow implementers were very aware of the TeX box model
>6.6.4 Inspiration from other formatting models In the early design phases of the CSS visual formatting model, other formatting models were frequently consulted for inspiration. In particular, TeX [Knuth 1984] was often brought up in white-board discussions.
As one of its foundations, TeX has a well-defined box model wherein all objects, including individual glyphs, are contained in boxes. The spacing between the boxes can be controlled through TeX commands. In addition to optimal spacing between boxes, TeX also allows maximum and minimum spacing to be expressed. This is referred to as glue (although Knuth suggests that springs is a better term [Knuth&Plass 1981]).
The visual formatting model in CSS is based on a box model, and all elements, both block-level and inline, are turned into boxes. Thus, CSS goes further than most other style sheet languages in creating boxes. For example, DSSSL and P94 do not use boxes for inline elements. However, CSS did not adopt TeX's glue. Although the issue was discussed, it was decided against in order to keep the VFM simple. Glue is very useful when breaking paragraphs into lines, but CSS leaves this problem to implementations. CSS allows, but does not demand, inter-paragraph line-breaking optimizations. Each box in CSS is, however, potentially richer than the boxes found in TeX since it can contain a padding, border and margin bands. CSS also borrows other features from TeX, including the em and ex units.
https://www.wiumlie.no/2006/phd/#ch-css
Håkon also goes into great detail on the historical context and technical requirements surrounding the development of CSS.
I think the reason the CSS box model differs from the TeX box model is because of the different problems they attempt to solve and various contextual difference the technologies developed under. Crucially, it's not because the authors were unaware of the TeX box model or because they thought they could do something better
Thanks, that's a nice read! (Note that I never suggested that CSS creators were unaware of the TeX box model, though I had no idea to what extent was it considered early on)
That also supports my claim that reflowing in TeX's box model would be simpler ("CSS is richer").
I simply highlighted a few things where TeX's box model does a great job for what are commonly hard problems with CSS even today (how do you align columns on decimal points?): I imagine most people, even LaTeX users today, are unaware of the internals, and I hope it's valuable for people to learn of them.
It's great thesis right?
It was never something I had thought about but I got curious so I did some digging.
It's true that some things are much easier in TeX, like aligning on decimals. HTML tables are used for all sorts of crazy so I'm not surprised it's harder to that kind of stuff
I want CSS to make spacing decisions so that I don't have to, and a system where graphical elements look good by default because they come with appropriate margins and containers adapt to what I want them to contain (e.g. by shrinkwrapping the content or by switching between different numbers of columns or rows) is more useful than a non-system that looks bad unless I keep many tedious ad-hoc specifications consistent.
Moreover,
> Margin breaks component encapsulation. A well-built component should not affect anything outside itself.
Definitely not, margins are an essential part of a component, like a garden around a house, and they make components "usable in any context or layout" (unless the context or layout is stupidly overcomplicated).
And then you have a container with A, B, margined apart by max(A.mr, B.ml) and want to insert a sizer knob in-between, which makes them (A.mr + B.ml) apart. Or you have to put ABCDE into a sortable wrapping flexbox, and the best practice* says you must -ml the flexbox, but now you don’t know how much because each of ABCDE has its own +ml. It’s all nice until it’s bullshit.
* which alone is utter nonsense
Margins make complete sense when you do want to fold them, which is common with textual content (eg. imagine an img tag with bottom margin, and a h2 tag right after it with top margin).
The fact that they can be misused does not make them useless.
Web styling is not useless, it’s just fucked up. The article has no hope to change that (neither do we) and tries to find a balance.
In a better world, margins would work in a block context (where paragraphs live) but not in flex/grid ones, which take layout in their hands. Another context could be called “constraints-based”, which would space its children relatively, like A.r + 10px <= B.l; B.r = this.r - 10px. Children would expose their preferred margins, which these layout contexts could take or not take into account.
The "block context" for which setting margins is a good layout abstraction is not the technical "block", "inline-block" etc. of CSS, but the general situation of measuring the empty space between homogeneous rectangular blocks.
If you want to place a slider between two text paragraphs, the slider isn't a good match for them (even if rectangular, it's far smaller) and you'll have to give it small or even negative margins under the assumption of the text paragraphs having normal, big enough margins.
And this highlights the issue with the idea that "children would expose their preferred margins, which these layout contexts could take or not take into account": a reusable and modular layout should be bottom-up, with components knowing what CSS rules make them look good and containers respecting them. Since the slider is small it can have smaller margins than the text paragraphs, but a container that knows that its middle child is a slider is a complicated and ad-hoc container.
If you want to place a slider between two text paragraphs
I don’t, I want to put a slider between A and B. I don’t know what’s there from the bottom-up rule. A or B may even have a loadable content, so I know nothing until it’s rendered. If A and B are paragraphs, that’s usually a coincidence irrelevant to the container’s purpose.
So what is the correct way to put a slider between beforehand unknown type A and type B components?
a reusable and modular layout should be bottom-up, with components knowing what CSS rules make them look good and containers respecting them
This approach is good until it’s not. You can’t just write off some layouts as “bad” and call it a day. It’s not modular, it’s pre-styled in a way that involves intimate knowledge about all of components and how they “stack” in advance, which is opposite to being modular.
It seems that I am discussing graphical design while you are discussing CSS anarchism and worst-case technical details. I mentioned text paragraphs because they are the most common and traditional type of block, but images and frames are popular since at least the middle ages and they should have about the same margins as text paragraphs (solid dark objects more, transparent images with internal empty space that adds to the "official" margin less).
The components around the slider are not "unknown": it's enough to be sure that, for example, their margins are between 20 and 30 px so the slider is allowed to claim -5px off a collapsed margin without collisions.
It seems you ain’t the one who has to look for the best turpentine, well, to each their own. Thanks for the info, I hope it will help with my next layout issue!
Exactly. Slapping some components together for a simple feature shouldn't require a designers input at all. Whether the defaults are margins or paddings I don't really care, but if the component looks bad without styling, it's a bad component.
> remake spacing decisions every time you use a component
I’ve worked with several awesome designers and they all appreciated when I told them this is untenable.
The word padding isn't in the article...
He reccomends spacing at the parent
The point about not having it in a component is valid. It happens all the time that a margin on a component is different based on context. But there is no need to make it any more complicated than a margin -- just add the margin externally and if your framework can't do that, add a wrapper and put the margin there.
For stand-alone components? Sure, that's good practice. But if you have a `section` with a `h3` inside of it, there's nothing wrong with using `margin-bottom: 1rem;` for that `h3` element. It's the best practice because it leads to less confusion that way.
Ban layout styling from your components entirely if you can. Make everything headless (eg https://headlessui.dev/, https://www.radix-ui.com/, plain old HTML, etc). Leave the way things look up to the app that uses them.
What if a user needs it ready-to-use? Also imagine you’ve created some styleless form which I need to style according to my Bulma or Bootstrap stylesheet. What if the structure is different than these two are assuming?
I'm not suggesting the layout shouldn't be included at all. There's nothing wrong with including a default for the component - as a stylesheet in "examples" or something. The point is that layout shouldn't be a part of the component itself. The beauty of CSS is that it makes doing that absolutely trivial.
The problem with CSS, and HTML is that content structure, content layout, and its styling is intertwined like spaghetti.
A ground up HTML rewrite is needed.
The problem is people treat HTML, CSS, and JavaScript like they are separate languages. They should be treated collectively like bytecode/binary and almost no one should need to know more than they exist, a compiler should be what's writing web output.
Are you saying that content and layout _should_ always remain combined?
I accept that we come to this understanding through our early introduction to language, writing and art. Most people intuitively link structure and presentation when they compose content.
Separating them is incredibly useful. You can see this at a really basic level, in plain text. Lay out a single long paragraph of plain text into lines of max 80 chars, and then insert a word. The layout goes completely wrong and if you don't have tools to help you, you spend ridiculous amounts of time realigning the text. The line-length algorithm you're following (or using) is __totally__ separate from the content. HTML and CSS are this, scaled up to allow fine grained annotation and control of both semantics and presentation, independently.
XSLT is there, chrome supports it, but no progressive rendering, or support of modern XSLT versions.
Basically, Google wants to kill XSLT, and normal web in favour of Javascript, and WASM based web. Why? They don't want people to turn off javascripts, so they can show more ads, and competitors cannot get an easier job making google.com alternative.
"Component" doesn't necessarily means "third party library".
The headless components need to be styled in the end, and the guideline present in the article still applies to those cases: outer margins in components causes some headaches.
"Component" doesn't necessarily means "third party library"
I didn't suggest it did. It does mean "block that could be reused somewhere" though. Devs should consider that carefully when they're making components. If your block can't be lifted and reused because it relies on the parent structure or it expects something global that's not passed in as a param/prop/whatever then you need to think some more, because you're making something that's not reusable.
One day, someone, somewhere will want to use your component in a way that you didn't plan for. If you've created a hard link to a particular part of your app such as an expectation that the parent HTML structure will look a specific way, then you've made their job harder when you didn't have to.
> Devs should consider that carefully when they're making components. If your block can't be lifted and reused because it relies on the parent structure [...] then you need to think some more.
Yes, that's what the article is talking about.
Even with headless components you need to have CSS somewhere else in the end. I don't know how you're "[leaving] the way things look up to the app that uses them" to headless components, but most people do it with (surprise) other components that belong to the "app" itself. Or with CSS-components.
So, the advice in the article could still apply to them. If you apply a style that contains an outer margin, you might run into the problems discussed by the articles.
I strongly disagree... That breaks the separation of markup and styling - and requires the markup being adapted for different styling. Pretty dumb solution for a non-existing problem... I don't even understand we you think (outer)margins are part of a component... Just don't but it into a components style if it isn't required or wanted ... That I may agree. But you realize you can add margins in a parent layout - like the page-layout and add and manipulate for the child components this way? Another of this considered harmful articles that need the byline - if you don't really know what you're doing and/or talking about...
I understand and agree with the sentiment expressed here regarding the outer layout of components in a page (though I'd actually recommend using `gap` with a flex or grid layout).
But margins can be incredibly helpful when implementing internals of complex components, and when laying out components next to each other that are not equal peers (like labels with form fields).
Negative margins can also be very helpful in some circumstances, like when a border shouldn't contribute to the space taken by a component, or when components should overlap without resorting to absolute or relative positioning.
I fully agree with the solution. Spacer components are a sound concept if you consider that a component should have no awareness of the larger context it is placed in. A next step to make components even more robust is for them to respond to available space in the parent container (container queries).
A spacer component is easy to understand and makes consistent spacing easy. There's nothing dirty or impure about it. You just mark it aria-hidden so that both search engines and screen readers ignore them.
Another tactic is to apply component margin spacing on individual instances of a component, as a utility class. It will work most of the time, but is more fragile.
There is but one issue: the internal padding of a component (which is perfectly normal to have) is visually experienced as a margin when two subsequent components share the same background color. In this scenario you're still theoretically consistent with your margins but visually it is not perceived as such.
You didn't read the article either huh
I did. What do you mean?
I mostly agree with this, but I would add a secondary construct on top of it. Sometimes you want collections of things to appear identical.
You might want a component, that as part of its own public API, abstracts away the usage of the spacing component (author calls this "Stack").
Ex: suppose you have a list of images and you always want them to be 8px apart. So you package up the Stack and an <Image /> component, that takes an images[] array.
This prevents the issue where engineers use the raw Stack+Image "atom" components and define random spacing rules: say the spec is 8, some engineers use 6, others 10, etc-- then you have inconsistent spacing in your app.
There's a use-case for more than a single approach. Margins and also collapsible margins are tremendously useful. In case of text of an article for example, where space changes based on other elements on the page (e. g. larger space above headings - the linked article also uses them).
In addition, if there's also CSS for `:first-child` that removes the leading margin and `:last-child` that removes the trailing, you can easily wrap them inside another component to remove the margins (or introduce a property to collapse them if you prefer). Of course, this is a bit more advanced and needs to be included in the documentation.
I'd strongly advise learning what modern CSS can do thanks to grid, flex, column-count, gap, and rem units before bloating DOM tree diffs with empty div tags.
Exactly. It seems like the conclusion of the post should really be: don't apply layout styles to elements if their parent elements are responsible for layout.
So ban a huge part of the CSS box model. Got it... Margins are valid in many use-cases, specifically many people leverage them for typography. They can even still be valid with component-driven development as well.
> So ban a huge part of the CSS box model.
Yes, why not?
While eliminating them is hard, the argument here makes perfect sense. We've all been fighting margins one way or another. One could even argue that negative margins are a symptom of the problem.
When you use negative margins you're pulling the element out of the box and essentially causing overflow. Does this fix the issue sometimes? Sure. Is it the best option? Maybe it should just be the exception.
> We've all been fighting margins one way or another.
Nope - not really.
There's always been a huge distaste for the box model and it comes from a place of not understanding it vs. anything else. Margins + features like flexbox are a huge feature of modern web development.
If I were interviewing a developer and they started to go off on "ban margins" it would cause the interview to go short.
---
Since you bring up an edgecase to defend the article: negative margins.
Negative margins are almost considered an edge-case use for my entire career which is as-old as the CSS spec. They would show up in situations where complex layouts were just not achievable (holy grail w/dynamic content width and static sidebars pre-flex/grid). The other place they would show up is simple "off-by-one" issues like placing a child div 1px over it's parent border in dropdowns etc.
I'd say for the last 5-10 years specifically they've not been needed and are an artifact of legacy development before flex showed up. If I came across code using negative margins in a PR I'd ask the submitter to refactor.
Idk - just saying that using an old edgecase as an argument against such a ubiquitous feature of the box model (which is core to FE development) seems off to me.
---
One last thought - margins are still perfectly valid with components. It's just that component developers don't want to think about how their component may lay out with something else on the page. It's microservices fiefdom on the front-end calling to give up core web development features because people don't want to think about components interacting with others.
Being a FE Developer myself I say there is no such thing as a standalone/full-encapsulated component in the FE world. Any non-trivial component always relies on, or uses outer contexts or side-effects. This could be non-scoped css stylings, translations, browser-environment specific state, global JS code/state, or networking state.
I like the approach that Cube CSS takes (https://piccalil.li/blog/cube-css/#heading-composition) where margins are considered part of the overall layout rather than at the component level
mmm, spacer elements. the new 1999 is looking pretty awesome.
"Oh it's out of alignment again? Just add a few more spacer.gifs in that table cell. This design was so great until we let people edit the content."
Spacer components described in the article and 90s spacer elements are not the same thing.
Here's the example linked by the article: https://seek-oss.github.io/braid-design-system/components/St...
Let's use <br> for vertical separation, just like the good old days!
Let's code all the apparence in the class attribute (instead of the style).
We do, its called tailwindcss lol (and its amazing)
Yes that was the joke (and I'm not a fan of it).
You didn't get the memo? We moved the style attribute inside the class attribute now
https://tailwindcss.com/docs/adding-custom-styles#arbitrary-...
Dear God
That's so sarcastic, when you use proper <table>,<tr>,<td> in your html, you don't need <br> ⸮
The article is not arguing for that...
Ironically, that's exactly how HN does spacing of the comments. The 90s will never die!
Agreed with avoiding margins, especially on reusable components, as they remove a degree of freedom for how they can be laid out by users. But these days, instead of margins or spacer components, I would recommend just using flex gap and grid gap across the board.
The difference being spacer components (and margins for that matter) don't wrap well, which is important for building fluid layouts that don't depend on hard-coded breakpoints.
I find that parent components specifying a gap between their children also happens to align much better with my mental model for designing layouts, not so much with spacer components or margins.
TL;DR: I haven't found any situation so far where I needed to reach for margins or spacer components ever since flex gap became widely supported. They're both dead to me now. Long live the gap.
Absolutely agree, the developments in flex and grid have done wonders for reusable components.
Same approach over here.
Flutter does this, and its layout model is quite well thought.
My thoughts too. Building UI's in Flutter is a delight, as opposed to web technologies which can be a nightmare.
If it’s still built on canvas, doesn’t that mean something about the state of web layout.
That's true. That being said, Flutter Web is not entirely ideal and although it has some good use cases (very simple forms and web apps that need to be developed quickly), I would primarily suggest Flutter on iOS and Android.
> For example, the Braid design system popularized the Stack component:
False, the Stack/Grid style component has been a maintstay of XAML based layouts for over a decade now, and at least generally speaking two decades old. Also, many web frameworks have had this for a while. Braid was not the first one. It's still a very well made framework though but the technical inaccuracy masks a whole load of history.
This is not a goods approach imho.
When I'm the html/css guy on a team, it makes my work super frustrating to have to deal with devs who preemptively try to break down everything into smaller bits. It gets in the way of iteratively improving my templates and css throughout a project.
The solution is simple.
First, you should use the "bottom margins only" approach wherever possible, cf. CSS best practice "use single directions margins only".
https://csswizardry.com/2012/06/single-direction-margin-decl...
If the Vue/React devs I worked this even understood just this, and put single direction margins in their components, there would be less issues already with collapsed margins and/or unintended vertical spacings.
Even better, just get rid of the spacing margins in the component altogether as the article suggest. But instead of creating more abstraction with "spacer" components, you simply add tailwind-style classes on your components such as list items, in the PARENT component's template.
Then the html/css guy till has full control over the templating, it's fluid and simple to edit, and there is no headaches with abstract "spacer" components that also add unnecessary complexity and indentation in the templates.
The main issue with "spacer" components is that in the end, the design is never this regular, There are always exceptions to the rule, and they are much more easily handled as I said by using atomic css classes like tailwind's `mb-*` in the PARENT template of the components you want to "space".
In my experience this approach accounts for ALL scenarios I've run into in a simple elegant way. The PARENT component of those you want to space can always have a bit of logic in React/Vue for those cases where the spacings differ from the default in the app.
Not that I entirely disagree that the parent component should be able to override margins and position, but I think spacer components abstract this a bit too far, and really isn't necessary at all if you can just use CSS. This is probably due to an avoidance of exposing HTML and CSS to developers in React.
Ideally the parent component would just have some styles that only effect the children, so you add this CSS to the parent:
x-item {
margin: var(--space-3) 0 0 0;
}
This works fine for custom elements (web components), but I think the problem is that in React you don't know what element a component is going to render, so it's generally much more difficult to target children this way.> This works fine for custom elements (web components), but I think the problem is that in React you don't know what element a component is going to render, so it's generally much more difficult to target children this way.
This is exactly the problem "spacer components" solve.
"A programmer does CSS"
Rules without margins are going to be a mess. For one you can't negative margin the padding out of the first titles in a section and other useful negative margin pulls.
Without margin collapse, you're going to need to handle first and last paragraph so they don't have half margin from their container.
Margins are respected across the elements hierarchy, which is especially useful if you're reusing components in react like systems; neither nested paddings nor grids aren't suitable do that.
What I would like is for a mobile site to both increase text size and reflow its contents as I pinch to zoom in on the site. Is it too much to ask ?
Use the browser text size controls instead.
how do you do this on mobile while browsing the site ? (and not jumping into chrome properties continually ?)
Isn’t it by chance in a site menu (an icon left to the domain name where a reader mode is)?
https://androidinsider-ru.cdn.ampproject.org/ii/w820/s/andro... — a green one here? (this is a hot link)
The author could have called them "layout components" and the whole thing would have made a lot more sense.
> Margin breaks component encapsulation. A well-built component should not affect anything outside itself.
I guess I should make all my programs stop printing anything , and creating any files, because I wouldn't want them effecting anything outside themselves.
I have also started using spacers more often, and applying margin much more sparingly. Flex with gap is also another margin alternative. Dunno about larger dom concerns; a handful of extra divs doesn't seem like a big performance concern to me
I haven't tried it yet, but this article reminds me of a CSS-only library with hstack and vstack elements: https://almonk.github.io/pylon/
This article acts like it's been a real challenge to get margins to work for the last 25 years. I have not had that experience. This seems like a solution in search of a problem, and likely a source of new problems down the road.
Mmmm nope. Margins and padding are hugely important for web usability and accessibility.
Polluting the DOM with spacer components (which are going to be styled with CSS anyway) as opposed to using grid, or flexbox, or just margins themselves (no float layouts please) is wasteful and needlessly complex.
"Banning margin" is excessive and clickbait-y.
The Spacer components idea is fine. Using Grid or flex layout in spacers is very sensible. But you could also use margins.
``` .spacer-stack * + * { margin-top: 1em; } ```
That's... what the article said. He's not saying don't use margin for anything, he's saying avoid giving your components margin. The Spacer component is literally just the CSS you posted.
There's something like 5 instances of clear rejection of the use of margins in the article and zero mentions of practical margin-based layout strategies. I stand by my assertion that the article is badly worded.
I think it's the language used that makes this sounds suspect. "Use layout components to control spacing" would be more clear about what the author is saying
Hum, just use `box-sizing: border-box` and you solve the problem that the author has without using a complete anti-pattern.
No, it doesn't. Margin may not affect the width and height of the element with `box-sizing: border-box`, but margin still breaks encapsulation and reusability of the component. You make layout assumptions by applying margin to a component.
Flutter does it.
Margins are part of the specification of CSS. Banning them would be crazy.
If you don't like thinking about them, sure .. introduce the concept of spacer elements in your framework. But outlawing something because you don't fully understand its purpose isn't the way to go.
I would recommend reading the article, or at least the top comments here. This is not what the article is advocating.
> We should ban margin from our components. Hear me out.
Maybe the first line of the article?
The implementation of the CSS spec is closely optimised for performance by the browser. Trying to achieve the same outcome by using other means, might feel like a clever way of conceptualising .. but I'd argue there's absolutely no need.
> because you don't fully understand its purpose
How did you arrive at the conclusion that OP doesn't understand margins?
Because the author has gone to such lengths to avoid them!
Creating extra markup specifically for presentation for example ..
This article is but one example of why I think React is actively harmful to the web. Its practitioners generally speaking have little interest or incentive to understand what's going on under the hood, and it shows. Its compiled code, except with exceptional teams, has no semantics and baffling, sloppy css.
The article's main point is a definitely worthy, arguable one: a React component perhaps should be entirely encapsulated, not affecting anything outside itself. Worth discussing, anyway.
The solution is sadly typical, though. Just grab another component! No interest in what `<Stack>` is actually doing?
How does it solve the problem? Does it use CSS? A single line in the style sheet: `display: flex` or `padding-bottom: 1rem` or something else like that?
This cultural lack of interest is why we have website source code littered with nonsense like `<div class="h1">` and `<div style="position:absolute;">`
I don't think it is fair to blame the tool for the mistakes of the operator. I feel like the real issue is that demand for software developers and specifically web developers is higher than than supply. Which leads to a situation where anyone who can put pixels on the screen, with at least some degree of reliability, will be hired and will put pixels on the screen. And they will choose whatever tool there is.
Another issue is that if look through job postings carefully, you will see the pattern there where framework knoweledge is valued above everything else. It is "React Frontend Developer" or "Vue Frontend Developer", not just "Frontend developer who is capable to pickup whatever technology we use".
There are reasons for that, of course, but it is hard not to see that this approach will likely skew hiring into looking for a specific knowledge in candidates. And candidates are going along with a path of the least resistance and learning stuff they need to work with backwards.
> I don't think it is fair to blame the tool for the mistakes of the operator.
I would ordinarily agree with you. After all, there is nothing inherent about React that prevents responsible HTML and CSS. And there's nothing about vanilla HTML and CSS that particularly encourages good practice, either.
Except. In practice. React abstracts so much away, that what looks like excellent, well-constructed code (in React) can easily compile into a mess that no one will see unless they go looking for it. And, in practice, I've worked with otherwise excellent teams who find my focus on semantics and CSS honestly baffling and "old school".
I think the rest of your analysis is spot on, though
In a nutshell:
.stack > * + * { margin-top: 1rem; }Literal laugh out loud. Thanks for that.
This is a great pattern. How does margins work in SwiftUI ?
No margins in SwiftUI, spacers and stacks.
There is an astonishing amount of css margin on that site.
Edit: The amount of oscillation this comment has had (between -1 and +1) is amazing, I wish I could see total number of up and downvotes..
am I the only one that think that the proposed solutions feels like late '90s/early 2000s with table layouts?
what's wrong with that? don't grid and flexbox feel like that as well?
Why are there so many new component libraries with the same ideas and syntax? I thought the example was from chakra-ui but it was from Braid.
Somebody invented grid before.
Why not padding, then?
I use <br> to get a “one line height sized” spacer all the time.
A more accurate title for this blog post would be “Ban Outer CSS Margins from Components”.
Bingo. Author will be fighting an uphill battle on HN because of the lack of specificity (an appropriate problem for a CSS article)
Or people could just read the very short article instead of instantly assume stuff based on the title here.
The article's own title is "Margin considered harmful" and the body finishes with the sentence "Ban margin."
The use of the ‘considered harmful’ trope should tell you al you need to know.
I did read the article and frankly it’s little better than the HN title. It’s heavily biased, fails to offer a convincing argument to anyone other than those who already believed the title, and even explicitly says margins should be banned.
Simply put, it’s not a good article.
> It’s heavily biased
What does "heavily biased" mean in this context? It's a straightforward article that puts forward a position, a series of propositions supporting that position, and a potential solution.
It's biased in that the author has an opinion he's trying to convince people of by providing arguments in its favour, but that's what writing tends to do (including your comment).
The "banning" stuff is obvious hyperbole[0]. No reasonable reader could conclude the writer means CSS margins should be banned in law.
You’ve admitted yourself that the article is highly opinionated and full of hyperbole so I don’t really see why you’re confused by my statement. But I’ll answer your question nonetheless.
Any article stating something should be “banned” can’t really be described as a balanced discussion about the usage of that thing.
I’m all for discussions on why usage of something is bad but in the case of margins there’s clearly use cases that are suited to it, which other commenters have highlighted but which the article entirely glossed over.
So the article is more than just written as a persuasive piece; it misses any balance or contextual clarity and thus ends up dishing out bad advice as a result. Bad advice that is so strongly emphasised that lesser experienced developers would easily be intimidated by it.
Now if people want to discuss the strengths and weaknesses of margins and put forward alternative solutions in the process, that at least informs lesser experienced developers about ways to solve their real world problems rather than berating them for not following some misguided idealised view of perfection.
(Yes I know there was some details about alternative ways to implement a margin, but they were crap and lacked any detail, since the article was essentially a rant piece rather than technical document)
Good luck with that…
This made me laugh out loud. Thank you.
Honestly I think this an amazingly effective title: It's very evident from the comments here who actually read the article (which is very short) and who are just replying to the title.
Yes, that title fits better with the content of the blog post and is a statement I would readily support. Leaving spacing to be the concern of the parent element makes eminent sense, and spacing components is a good way to do that which happens to be used by drag-and-drop editors like Squarespace.
(Replying to myself to give some context: The original title of the post and here on HN was "Ban CSS Margins".)