How well do you know CSS? (+ mini-test)

image



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.



Pass the test



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; /* Take this */ height: 50px; padding: 5px; /* Plus this x2 for left and right */ border: 1px solid red; /* Plus this x2 for left and right */ background-color: red; /* = 112px of computed width */ }
      
      





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:





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:



  1. Use the box-sizing property for the footer directly or via the reset mentioned earlier

  2. 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/GPNmbM

Note : 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:



  1. 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.

  2. 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.






All Articles