Understanding CSS Percentage

Khang - Sep 8 '21 - - Dev Community

Have you ever failed to understand how the percentages work in CSS? Ever wondered why it's so messed up and has zero logic sometimes? Well, I do. That's why I'm writing this post to share my understanding through my researches and readings with you.

Percentage of what?

As a percentage, obviously there should be a target taken as a reference source. Most answers to this are the parent block of the element we assign the percentage. This is correct, but does not entirely cover all the cases. The most correct answer should be the containing block, meaning the block that contains our element and it doesn't have to be the direct parent.

Let's take a look at the below example:

In this example, I created 3 nested divs, which are 3 squares with the following characteristics:

  • The outmost grandparent div has a lightgray color with a size of 4x4
  • The parent div has a darker gray with a size of 2x2
  • And the red child div which I assigned the size of 50%

If the percentage unit took the parent as the source, the size of the child should have been 1/2 of it, but no, the child actually has the size equals to the parent and 1/2 of the grandparent as you can see. The reason is the grandparent div is the true containing block of the child div, due to the fact that the child has position: absolute, corresponding to position: relative I have set in the grandparent.

Therefore, to identify which is the actual containing block of an element, it's entirely based on the position property of the element itself. You can read more on MDN.

However, for certain properties, the reference source for the percentage unit is neither the parent nor the containing block, instead, it is itself - self element.

Percentage by property

width/height

Pretty straightforward, as you have seen in the example above, when an element is assigned a percentage value to its width, the width of the containing block is taken as the reference source. Likewise, height of the element refers to the height of the containing block.

padding

For padding, either vertical (padding-top/padding-bottom) or horizontal (padding-left/padding-right) refers to the width of the containing block.

Example:

In this example,

  • The parent div has a size of 6x4.
  • The child div has a size of 0, but with padding-top and padding-left given 50%

The result is that the child has a size equivalent to 1/2 width of the parent, which is a 3x3 square.

margin

Similar to padding, the percentage of margin (both vertical and horizontal) refers to the width of the containing block.

Example:

In this example,

  • The parent div has a size of 6x4.
  • The child div with margin-top and margin-left given 50%

The result is that the child is positioned 3 units away from the top and left margins of the parent (1/2 width of the parent).

top/bottom/left/right

For these properties (usually come with position), the vertical ones (top/bottom) refer to the height and the horizontal ones (left/right) refer to the width of the containing block.

Example:

In this example,

  • The parent div has a size of 6x4.
  • The child div has position: absolute with top and left given 50%

The result is that the child div is positioned 2 units away from the parent's top edge (1/2 height of the parent), and positioned 3 units away from the parent's left edge (1/2 width of the parent).

transform: translate()

An incredible property for animation/transition, it also supports percentage value. However, this one does not refer to its containing block, but instead refers to itself.

Example:

In this example,

  • The parent div has a size of 6x4.
  • The child div has a size of 2x1 with transform: translate(50%, 50%)

The result is that the child div is positioned 0.5 unit away from the parent's top edge (1/2 height of itself), and positioned 1 unit away from the parent's left edge (1/2 width of itself).

background-size

The background-size property brings the complexity of percentage unit to a new level 😄
The percentage value of this property now refers to the background positioning area, which I interpret as similar to the containing block, but with an addition of these 3 factors:

  • Block with only content (content-box)
  • Block with content and padding (padding-box)
  • Block with content, padding and border (border-box)

The 3 factors are given by the background-origin property. You can read more on MDN.

Example:

In this example,

  • The parent div has a size of 6x4.
  • The child div has a size of 3x2, no padding, no border
  • I used a DEV logo (with the ratio of a square 1:1) as a background-image for the child div, with the background-size property set to 50% 50%

The result is that the background image has been stretched to have a size of 1.5x1, corresponding to 1/2 size of the child.

background-position

Similar to background-size, the percentage of the background-position property also relies on the background positioning area.

Example:

In this example, the same image and layout was used as the previous. As we changed the value of background-position, some observations we can see:

  • Without any value (by default, the value is 0 0), the background image is positioned at the top left corner.
  • With background-position: 0 50%, the background image is positioned left center.
  • With background-position: 50% 50%, the background image is positioned in the center.
  • With background-position: 100% 100%, the background image is positioned right bottom.

Note: background-position: 0 50% is equivalent to:

  • background-position-x: 0
  • background-position-y: 50%

Apparently, there are some calculations behind the percentage of this property, instead of just the distance between the image's top and left edges to the child's. Through some researching and testing, it appears that the background-position property relies on the following calculation before yielding an actual value:

offset X = (container's width - image's width) * background-position-x

offset Y = (container's height - image's height) * background-position-y

In this case, with

  • container as the child div
  • image's width/height is the resulting size of background-size

font-size

For font-size, the percentage value solely refers to its direct parent block.

Example:

In this example, I use the same layout as the very first example, with the font-size assigned as below:

  • 13px for grandparent
  • 26px for parent
  • 50% for child

As a result, we can clearly see that the font-size of the child is now equivalent to the grandparent and is 1/2 of the parent, ignoring the fact that the position: relative is assigned to the grandparent, not the parent.

line-height

While might not be as popular, I also mention this property as it also supports percentage. The percentage value of line-height relies on the font-size of itself.

Example:

In this example,

  • The paragraph has 11 lines
  • font-size is set 20px
  • line-height is set 150%

The actual height of the whole block is ~329px,

  • The line-height in this case is: 20 * 150% = 30px.
  • The height is then: 30 * 11 = 330px, approximate to the actual height.

Takeaways

Hope the article cleared up some of your understanding regarding the percentage value in CSS instead of making things worse 😅

I also tweeted a cheatsheet to summarize what is written so far, maybe it will come in handy to remember the meanings at first:

And this is a collection of all the examples in the article: https://codepen.io/collection/xKwgdW

References

. . . . . . . . . . . .