CSS can be hard to grasp when you're starting out. It can seem like magic wizardry and you can very easily find yourself playing whack-a-mole adjusting one property only to have something else break. It is frustrating, and that was my experience for quite a long time before things suddenly seemed to "click".
Reflecting back on this time, I think there are a few key concepts that were vital to things finally all making sense and fitting together. These were:
box-sizing
, height
, width
, margin
, padding
)display
)position
, top
, left
, etc.)There are also some useful concepts to keep in mind when building reusable and composable components.
A key way I think about building visual UI components is that basically everything can be broken down into a bunch of rectangles on a page. It can be overwhelming to consider all aspects of a page at once, so break things down mentally into a series of rectangular components and ignore everything that doesn't matter for the piece that you're working on at that point.
When you consider the size of an element (i.e. its height
and width
), there are two different models by which to measure this and you can adjust this with the box-sizing
property.
box-sizing: content-box
(browser default)The size of an element only includes its content, and not its padding
or border
.
box-sizing: border-box
The size of an element is inclusive of its padding
and border
. When you set width: 100%
with content-box
, the content will be 100% of the width of the parent element, but any borders and padding will make the element even wider.
border-box
makes a lot more sense, and I find it much easier to reason about. To apply border-box
to all elements on the page, make sure you have something like this snippet in your stylesheet (a lot of CSS resets will include this anyway):
css*,::before,::after {box-sizing: border-box;}
Going forward, I'm only going to be considering border-box
, as it's our preferred box model at Kablamo.
margin
values sometimes collapse with an adjacent element's margin
, taking the maximum of the margins between them rather than the combination of both. The rules around this can be somewhat complex, and MDN has a document describing them: Mastering margin collapsingmargin
and explicit width
/height
don't work on inline contentmargin
is not applicable to table cellsElements are usually laid out in the document in the order that they appear in the markup. The display
property controls how an element and/or its children are laid out. You can read about display
in more detail at MDN.
display: inline
allows content to flow kind of like text and to fit with other inline content, sort of like tetris piecesdisplay: block
means the element effectively behaves like a rectangle containing all of its children that grows in height to fit content (width
is 100% of the parent content box by default). Effectively, line breaks are inserted before and after the element.display: inline-block
is like a mixture of both inline
and block
. Its contents will be contained within a rectangle, but that rectangle can be laid out as part of inline content.display: flex
and display: grid
are more advanced layout algorithms for arranging children according to certain rules. These are the bread and butter of building flexible, responsive layouts and are well-worth learning about in more depth. Learning these has been gamified in Flexbox Froggy and Grid Garden.The position
property affects how elements are positioned with respect to the flow of the document in combination with positioning properties (top
, left
, right
, bottom
, inset
).
position: static
by default, which means that positioning properties have no effect on the position of the element and the element is laid out normally. Statically positioned elements also do not have their own stacking context, which means that setting z-index
will also have no effect.position: relative
is like position static, except that top
, left
, and other positioning properties act like an offset of where the element should be visually laid out (although sibling and ancestor elements will behave as though it is still in the original position).position: absolute
takes the element out of the flow of the document and ancestors/siblings will be laid out as if the element were not present. Positioning properties specify offsets of where the element should be visually laid out relative to the nearest non-static
parent. You can add position: relative
to an ancestor to use it as the anchor point for an element with position: absolute
.position: fixed
along with positioning properties means that the element is removed from the normal flow of the document and instead laid out relative to the viewport. If positioning properties are specified, they will ensure the element is laid out always with that offset to the viewport (i.e. they appear fixed in place and don’t move, even when scrolling).position: sticky
is a bit more complex, but you can think about it as a hybrid of position: relative
and position: fixed
. You can read more about the exact mechanism at MDN.The visual aspect of reusable and composable components should ideally be self-contained. Outer margins are generally a bad idea. Margin collapse on outer borders means component layout is more complicated to reason about and the effective boundary is not the same as the visual boundary (such as a border). This can mean that components behave less predictably and you can no longer think of them as just a rectangle.
Essentially, you want to make your components easy to just include in an application without worrying if they break outside of their simple rectangle.
Overall, I think these are most of the guiding principles that I wish I understood a lot sooner than I actually did. I spent a lot of time fumbling around, tweaking CSS back and forth, and not really understanding why things weren't behaving as expected.
Eventually it did "click" for me and I felt like I suddenly understood things.