Why I try to avoid CSS margins

2023-12-31

Margins are effectively side effects. They affect the positioning of elements outside of their scope, meaning they break encapsulation. As such, they should be avoided whenever possible.

Instead of using margins on elements to lay out the elements in a page, a parent elements should define the layout and spacing of your page. You can do this simply by using flexbox or grid with gap, or defining margins for children with > * + * (this is okay, but I think this is less preferable to just using flexbox with gap because often you would want the last-child to not have a margin in which case you would have to be even more specific with > *:not(:last-child) , which is already much more complicated just defining a gap).

Margins should only be used when the element it is being applied to can only be used in a specific context (it is not reusable across different contexts). Because margins affect the elements outside of its scope, the context in which it is used must be understood wherever a margin exists, which is annoying because it increases the cognitive load for developers when reading the code.

But on the other hand, this means using margins is valid only in contexts where its reusability is in a specific context - such as for components that are composed of several subcomponents. Take the following Card component for example:

function Card({children}) {
	return <div>{children}</div>
}

function CardHeader({children}) {
	return <div className="m-2">{children}</div>
}

function CardContent({children}) {
	return <div className="m-3">{children}</div>
}

function CardFooter({children}) {
	return <div className="m-4">{children}</div>
}

Used like this:

<Card>
	<CardHeader>
		Card header
	</CardHeader>
	<CardContent>
		Card content
	</CardContent>
	<CardFooter>
		Card footer
	</CardFooter>
</Card>

If the parts that make up the Card have different values for spacing between them, then we can’t just have one spacing value in the container ( Card ). In addition, since the subcomponents in the Card component should only be used as children of Card , using margin here is valid.

However, even in this scenario, margins can be avoided by using padding instead. The only potential downside to that here would be that it might feel a little awkward to visualize mentally because padding is the space inside an element but we are using it to define the space between elements. But then again, using margin anywhere at all could also be considered a downside for the reasons already mentioned so I consider using padding and margin in this context to be equally valid.

function Card({children}) {
	return <div>{children}</div>
}

function CardHeader({children}) {
	return <div className="p-2">{children}</div>
}

function CardContent({children}) {
	return <div className="p-3">{children}</div>
}

function CardFooter({children}) {
	return <div className="p-4">{children}</div>
}

As you can see, by avoiding margins except in a small minority of use cases, we are encouraged to build better components that are void of side-effects and as a result more reusable, readable, and maintainable.