We have a few items in a series, and we control the spacing by giving each item its own bottom margin, except the last. Perhaps we have a form, and we give fields bottom margins, as well as labels. Instead, consider spacing out the items by defining them as children of a parent, where the parent controls the space between its children.
To expand this principle more broadly, containers and content should both be spaced by spacing parents. Content should be spaced by simple listing or repeating objects, and containers should be spaced by layout objects such as grids, tiles, or blocks. In rare cases, one-off spacing shims will be useful, but most spacing can be defined as a relationship between a group of children.
.form--vertical > :not(:last-child).field--vertical > :not(:last-child)
A container should be nothing more than a slot for the content that goes into it. Consider a card: we can assume a card will always have a certain style of heading, or we can instead provide a slot for a heading to be rendered within. In nearly every case, the latter will prove the correct choice.
Containers should style padding, borders, background, etc. They shouldn’t be aware of foreground content. For bonus extensibility points, consider making regions within a container optional; for example, a card could be used without a header or footer.
A CSS object should be relatively flat. Once we exceed a couple layers within a single object, it’s likely to have mixed concepts best abstracted into their own concerns. Don’t think in terms of business concerns, but design and layout abstractions. Compose CSS objects to form business components, which can be encapsulated with our rendering layer as a SPA framework component or server-side partial.
To put it concisely, if a page layout knows how to style a button, it’s too deep. Make objects configurable, and the composed component template can configure them; don’t allow for parents to alter the style of descendants. Avoid selecting deeply or across objects.