Space Principle
The difference between pixel perfect and good enough, mostly boils down to perfect space management. Here we discuss the space principle.
- Fundamental rule: Don't camp on external space.
In CSS terms: Components should not have external margins.
Issues with external margins
- Fundamentally external: Margins are outside the width of the component in the recommended
border-box
box sizing (width of the component only includes the content,padding and border). When you add a margin to a component it ends up taking responsibility for screen area it does not control:
- Margins collapse: You set a value, but it renders as something else depending on the neighbours. E.g. the top margin on the bottom component in the example shown will disappear. This can be a maintainability nightmare.
- Creates cross component dependencies: Suppose you want to conditionally render some buttons. If you are using component margins for space, then there is a conditional logic dependency leaking into your CSS. These dependencies quickly become unmanagable.
- Makes pixel-perfect placement impossible: Margins on components disable pixel-perfect placement as they push themselves away from borders.
So the Question: If the components are coming without margins, how are they being spaced from their neighbours e.g. how is the margin-free button and the input spaced?
Lets discuss that next.
How to do space correctly
Realize that a component's external space, is its parent's internal space. A parent is free to manage its internal space anyway it wants. So really, space management between components is the responsibility of the container (and we provide plenty of such containers).
Space between children: the space
prop
Remember good components don't come with bleedy margins. Since components don't use margins, our containers can easily create space between their children by adding margins with pure CSS (pseudocode: CSS-selector(all children except first-child): CSS-property(margin)
).
Lets look at some examples of external-margin-free components and how they nicely compose. First a few simple margin-free components e.g. a basic react label
and an input
(not a part of this library, so you can use your own):
Because they are both margin free, they can easily be reused in any given context. We can easily compose them to create a Column(label+input)
set:
In the above example, notice:
- the space is coming from the parent
Column space=10
. So ourinput
andlabel
don't have to fight each other to see whose margin wins. - the Stack itself has no external margins. So the combined
Column(label+input)
is once again resuseable without bleeding.
We can easily compose multiple Column(label+input)
into another Column
, demonstrating creating a composite of composites e.g. 3 x label+input
:
Notice that this composite of composites (Column( 3 x Column(label+input) )
) is also external margin free, and we can continue down this path of reuse. We'll stop here, but you get the point 🤗.
Space at the border: the padding
prop
We've talked about spacing children among each other, one final thing to talk about is how to seperate children from the parent border. The answer is to simply use padding on the parent:
Here are some best practices for padding. You normally:
- don't put
padding
on any layout components that you want to compose into other layouts. - put
padding
on page level layout components. - put
padding
on components that have an explict visually designed border (e.g buttons).
So a page level form layout would have padding, whereas a reusable form-component (or reusable form-component-set) would not. You can observe this pattern in the example below where the two Column
s for the reuseable First/Middle/Last
name component do not have a padding - enabling seemless reuse i.e. visually it looks like a single form: