
The difference between successfully using CSS and painful attempts to deal with it often depends on small details. In fact, CSS has a lot of nuances. One of the most common areas where I often notice such a struggle is styling layouts. Personally, I like to learn CSS patterns. I noticed that I tend to use a small number of them to solve most problems with the layout. This article is about those CSS patterns that I use to overcome layout issues. Situations will be considered regardless of the CSS methodology used: be it SMACSS, BEM, or even a hot CSS-in-JS topic, because they all focus on the CSS properties themselves, and not on the architecture, organization, or strategy.
For fun, let's start with the test.
We will use the platform I developed, called
Questionable.io, and I used it to create a test, which we will go to below. Do not worry, no personal data is collected, the results are anonymous and it is absolutely free.
The purpose of testing is to see if you can recognize specific CSS behavior and some problems without first reading the article. I was not going to make testing difficult, but the nuances of CSS styling can be quite complicated. Remember, this testing is just for fun. The test results do not demonstrate your coolness, but I hope they will still be useful.
The test consists of 10 questions and should take no more than 10 minutes
From the translator:
At the end of the article on Habr, a poll about the number of points scored in the test is added.
Note: if you do not want to waste time,
here is a link to questions with the correct answers.
Did you pass the test? Fine! Let's go through the questions one by one to get a better idea of the styling patterns that are affected in the test.
Question 1: Block Model
Learning the block model should be a priority on the list of any FrontEnd developer. Maybe the article “
The CSS Box Model ” is a bit old, do not underestimate its value and relevance for modern CSS. The block model is the foundation for almost every issue related to CSS layouts.
This particular question checks how to find out the width based on the principles of the block model. The block has an explicit width through the
width: 100px
, but it turns out that the
default block model rules apply the
width
properties to the “content” layer of this block. The resulting width (how wide the block is drawn on the page) is the sum of the content, padding and border layers. For this reason, the answer is 112px.
.box { width: 100px; height: 50px; padding: 5px; border: 1px solid red; background-color: red; }
If you are faced with a situation in which the last column or tab in the interface is moved down to the next line, although you specified
width: 20%;
for all
width: 20%;
and were sure that they would fit in 100% of the parent element, probably this was becoming a problem. Five columns with a width of 20% are placed in 100%, but if they are additionally given padding and / or border, this will increase the width of each, leaving not enough space in the current row for the last column.
When CSS3 was introduced, a new tool called
box-sizing
. It allows us to control which layer of the block model we want to apply the
width
property to. For example, we can specify
box-sizing: border-box
. This means that we want any width rules to be applied to the outer “border” layer instead of the “content” layer. In the question from the test, if the
box-sizing: border-box
property were applied, the resulting block width was 100px.
For some of you, this is a well-known fact, but still it will be a good reminder for both professionals and beginners.
There are a number of articles about the block model and how to use the box-sizing property as a reset to apply it immediately to the entire project.
Box Sizing and
Inheriting box-sizing Probably Slightly Better Best-Practice are two good CSS-Tricks articles to read.
Question 2: Element Boundaries
The second test question can be partly seen as a continuation of the first. Remember, it’s one thing to read: “The block model has layers (content, padding, border) and they all affect the final width and height of the element”, another thing is to be able to recognize the problems of the block model in a real situation. This particular problem is a bit of a classic among those who have been working with CSS for some time. It follows from the fact that the frames will occupy a certain additional space and push away the surrounding elements, since they are part of the block model. Adding frames while changing the state of an element, such as
:hover
, will mean that the blocks will become larger and then the blocks that follow will be pushed down. This
can be annoying :
Of all the possible solutions mentioned in the test question, adding
border: 2px solid transparent
to the original state (without hovering) is the only possible solution to the problem.
box-sizing
does not solve this problem, because we do not explicitly set the height. If we did this, the frame would become part of the overall height of the element and no shift would occur, but this is not our case.
There are other solutions that we did not mention in the answer options. One of them is adding a pseudo-border using the
box-shadow
property or using
outline
instead of
border
. Each of them will not lead to bias, since it is not a layer of the block model. Here's another
CSS-Tricks article where you can read more about such solutions.
Note:
Remember that
outline
does not support
border-radius
Question 3: Positioning - absolute vs fixed
Besides understanding when to use each of the types and
how they differ in the visual representation , it is also very important to know the rules of how each of the positioning methods is bound to the parent element using the top, right, bottom, or left properties.
First of all, let's look at the
containing block . A brief definition is that the containing block is most often the parent of any element in question. However, the rules for the containing block are different for absolutely and fixedly positioned elements.
1) For absolute elements . The containing block is the closest ancestor whose positioning is not
static
. For example, when an element is absolutely positioned and contains the
top
,
right
,
bottom
or
left
properties, they will be positioned relative to any parent that has
absolute
,
relative
,
fixed
or
sticky
positioning.
2) For fixed elements . The containing block is the viewport, despite the presence of any parent elements with positioning other than
static
. In addition, the scroll behavior is different from absolutely positioned elements. Fixed elements remain “fixed” in the viewing area, hence the name.
Many developers think that absolutely positioned elements only look for the closest parent element with relative positioning
position: relative
. This misconception has become widespread simply because
position: relative
most often paired with
position: absolute
to make a containing block. The reason this is often used is that the relative positioning of the parent block leaves it
in the stream , which is often preferred. There are situations when the parent of an absolutely positioned element is itself absolutely positioned. This is completely normal. If all parents are static, then an absolutely positioned element will be attached to the viewport - but so that it scrolls along with the viewport.
There is a little-known addition to the rules mentioned above: in situations where the parent element has the
transform
property (
among others ) with a value other than
none
, it becomes a containing block for absolutely and fixedly positioned elements. Confirmation of this can be seen in CodePen, where the element with the text “Notice!” Is a fixed element, and the parent has the transform property, but only when the mouse cursor is over it (in state
:hover
)
Question 4: Collapsing margin of parent and child elements
This is one of those CSS little things that can cause a lot of problems if you don't know how it works. There is a concept called collapse margins and many people are familiar with the manifestation of this, called collapse margins of adjacent siblings. However, there is another form, called the Collapse margins of the parent and the first / last child, which is less known. The following is a demonstration of both cases:
Each paragraph tag has a top and bottom margin of 1em, which is styled by the browser. So far this is the easiest part of the situation. But why is the spacing between paragraphs not 2em (the sum of the top and bottom)? This is called collapsing margins of adjacent siblings. Margins overlap in such a way that the larger of the two margins will be the size of the gap, so in this case the gap will be 1em.
However, something else strange is happening. Did you notice that the top margin of the first paragraph does not create a gap between it and the blue container div? Instead of breaking, it seems to embed the margin in the parent div, as if the div had a top margin. This is called the Collapsing margins of the parent and the first / last children. This type of collapse margins will not occur under certain circumstances if the following is characteristic of the parent:
- There is an upper / lower padding greater than zero
- There is an upper / lower border greater than zero
- A block formatting context is specified that can be created using
overflow: hidden
or overflow: auto
- Display: flow-root property set (poor browser support)
When I am happy to explain to people this small CSS detail and solve it with padding or border, the answer is almost always: “What about padding or border equal to 0?”. Well, this does not work, because the value must be a positive integer.
In the previous example, 1px padding allows us to switch between using collapse and preventing collapsing margins of the parent and child. The gap that appears between the first / last paragraph and the parent is 1px padding, but now margin is considered the inside of the container, because the padding layer creates a barrier to prevent margins from collapsing.
As for the question, I'm sure you can see what the problem is in this interface:
The first element with the
.comment
class (without the
.moderator
class) collapses margins. Even without looking at the code, we can see that the element with the
.moderator
class has a frame, and the element without this class does not. There were actually three answers on this question that were considered correct. Each of them, in fact, is already applied in the CodePen source, they are just commented out.
One of the reasons this type of collapse margins is not as widely known as the others is because there are many situations in which we can accidentally avoid this. Flexbox and Grid elements create a block formatting context, so when using them we do not encounter this type of collapse margins. If the interface of our comments was a real project, chances are good that paddings were set on all four sides to leave space around, and this, in turn, fixed the collapse of margins for us.
Question 5: Percentage of what?
When percentage units are used, percentages are based on the width or height of the containing block (usually the parent). As we said earlier, an element with the
transform
property will become a containing block, so when the element uses transform, the units of measurement in percent (only for
transform
) are
on the element’s own size, and not on the parent size.
In the example below, we can see that 50% take on two different values depending on the context. The first red block has the
margin-left: 50%
property
margin-left: 50%
, and the second red block uses
transform: translateX(50%)
.
Question 6: The block model strikes again ...
Only you thought that we had finished talking about the block model ...
The problem is due to the fact that we use
width: 100%
for the footer and at the same time add padding. The width of the container is 500px, which means that the footer content layer (being 100%) is also 500px before paddings are added to this size.
The problem can be fixed by one of two common techniques:
- Use the box-sizing property for the footer directly or via the reset mentioned earlier
- Remove the
width
property from the element and use the left: 0
and right: 0
properties instead. This is a good example of using the left
and right
properties at the same time. This approach avoids the problems of the block model, because the width
property will use its default value auto
to fill any available space between padding and borders when left: 0
and right:0
.
Note: One option was to remove padding from the footer. Technically, this would fix the problem because the content layer would be 100% and not have padding or border to expand beyond the width of the container. But I think this solution is the wrong approach, because we do not have to change our interface in order to adapt to the problems of the block model, which are easily fixed without it.
The reality for me is that I always have the
box-sizing: border-box
property
box-sizing: border-box
as part of my general style reset. If you do the same, you most likely rarely encounter this problem. But I still like the technique with setting the properties
left:0
and
right:0
at the same time, because, as the time shows, it is more reliable (in any case, judging from my experience) than solving the problems of the block model that arise due to the width one hundred%.
Question 7: Centering Absolute and Fixed Elements
Now we are really starting to combine all the material described above, with the centering of the absolute and fixed elements:
Since we have already covered most of the material in this test question, I simply indicate that horizontal and vertical centering can be done using the old-school method via negative margins, or newer using the transform property. I also bring to your attention an excellent
CSS-Tricks element centering guide .
Note : some time ago it was argued that if we know the width and height of the block, we should use negative margins because they work more stably than the new transformation property. At the moment, the transformation works stably and I almost always use this property, unless I need to avoid converting the block to the containing one.
Question 8: Centering elements in a normal flow
Flexbox brought us many amazing tools to solve complex layout problems. Prior to its release, it was reported that vertical centering was one of the most complex features implemented in CSS. With the advent of Flexbox, vertical centering has become routine:
.parent { display: flex; } .child { margin: auto; }
https://codepen.io/bradwestfall/pen/GPNmbMNote : notice that for flex elements, margin: auto is applied to the top, bottom, right and left side to center the element vertically and horizontally. Previously, vertical centering did not work for block elements, so
margin: 0 auto;
quite common
margin: 0 auto;
Question 9: Calculations with different units
Using the calc () function is great when you need to work with two units of measurement that we cannot add up on our own or when we need to make fractions
easier to understand . This test question offers us to find out what the result of the expression
calc(100% + 1em)
will look like, starting from the fact that the width of the div element is 100px. This could be a bit confusing, because in fact, it doesn't matter that the width of the div element is 100px. The percentages always start from the width of the parent, so the correct answer was: “100% of the containing (parent) block plus 1em”.
There are several situations in which I regularly use
calc()
. Firstly, whenever I want to shift something by 100%, but also add a fixed amount of extra space. Dropdown menus can be a good example of this:
The peculiarity here is that we want to make a drop-down menu that can be used together with the calling elements of different sizes (in this case, two different sizes of buttons). We do not know what the height of the element that calls this menu will be, but we know that
top: 100%
will place the top edge of the drop-down menu at the bottom edge of the calling button. If each menu should be at the bottom edge of the corresponding button, plus 0.5em, this can be achieved with
calc(100% + 0.5em)
. Of course, we could also use
top: 110%
, but these additional 10% would depend on the height of the calling button and container.
Question 10: Negative margins
Unlike positive margins that repel siblings, negative margins draw them closer to each other
without shifting siblings . This final test question offers two solutions, both of which technically eliminate the double border in our button group, but I strongly prefer the negative margin technique, because deleting the frames would make it more difficult to perform certain techniques, such as the mouseover effect.
The resulting effect is a “common border” that is displayed between the buttons. In fact, buttons cannot have a common border, so we need to use a negative margin technique so that one frame overlaps another. Then I use
z-index
to set which frame I want to fit above another, based on the state of the mouse hover. Note that
z-index
is useful here even without using absolute positioning, but you had to set
position: relative
. If I used the technique of removing the left frame of the second button, this effect would be much more difficult to implement.
It all makes sense.
I want to show you another last demo that uses a lot of tricks that we discussed before. The task is to create tiles that expand to the left and right edges of the container, taking into account the indentation. By tiles, I mean the ability to have a list of blocks that wrap to the next line when there is not enough space in width. Hover over the tiles to see the result:
An obstacle in performing this task is indentation. Without padding, it would be easy to get tiles adjacent to the left and right edges of the container. The problem is that indents will be created using margins, and when we add margins on all sides of the tile, we create two problems:
- The presence of three tiles with a width of
33.33%
in conjunction with margin will not be able to fit in a row. Although box-sizing
allows us to have padding and borders on the .tile
element and fit in 33.33%
, this will not help us in the case of margins - which means that the calculated width of the three tiles will be more than 100%, which will force the last tile to move to the next string.
- The left and right tiles will no longer lie against the edge of the container.
The first problem can be solved with calc((100% / 3) - 1em)
. Which means 33.33% minus the left and right margins of each tile. The collapse of adjacent sister elements will not occur here, since it is possible only at the upper and lower margin. As a result, the horizontal distance between two tiles is the sum of two margins (1em). In this case, the collapse also does not apply to the upper and lower margins, because the first and last tiles are not technically adjacent, even if visually one is under the other.By taking withcalc()
Three tiles can fit in a row, but they still don’t touch the edges of the container. To do this, we can use negative margins in the size equal to the left and right margins of each tile. The green dotted line in the example is a container where we apply negative margins to draw tiles corresponding to the edge of the surrounding content. We can see how they fit to the padding area of the parent element. This is normal because negative margins do not repel surrounding neighboring elements.As a result, we get tiles that have sufficient spaces that expand from edge to edge so that they align with adjacent paragraph tags outside the tiles.There are many ways to make tiles (and usually they have their advantages and disadvantages). For example, there is a fairly elegant CSS Grid solution that Haydon Pickering talked about. This solution is implemented using a technique that simulates requests for containers (but using Grid magic). Ultimately, his solution using Grid is better than flexbox, which I demonstrated but has worse browser support.Summary
In the beginning, I stated that I was inclined to look for patterns in solving problems. This article is not so much about the scenarios demonstrated above; it is more about a set of tools that can be used to solve these and many other typesetting problems that we can all face. I hope these tools help you.By the way, there are a number of excellent resources that carefully examine the topic of the block model. First of all, articles by Rachel Andrew and Jen Simmons, which are definitely worth reading.- Box Alignment Cheatsheet is a great material with visual elements that emphasize various properties that affect how the elements align either on their own or relative to other elements.
- Jen Simmons Labs — , .