How to calculate padding-top in % of the given div relative to its parent div

user5589607 picture user5589607 · Jan 5, 2016 · Viewed 14.7k times · Source

Problem: I want to add padding-top in % to all list items (because I need to move the text a little down and also need all elements to be scalable), here is jsfiddle code

Height works fine. In order to calculate height I calculated the percentage of li height(which originally was 40px) relative to its parent ul(which was 400px). 40px out of 400px = 10%. It seems to work fine after that total ul height is still equal to 400px. (box has height of 53.33vw bacause 400px is 53.33% of 750px).

But the problem is with calculating padding-top in %. In order to calculate padding-top (which was 8px) I needed to calculate 8px out of 400px(400px is the height of the parent ul) = 2%, and as in fixed layout I subtracted those 2% from height (10% so height was now 8%). But after that the total ul height is equal to 470px. How to calculate padding-top in % so that height of ul still will be 400px? I need to preserve those 400px which was the original height of the ul, after adding the padding-top in % the height of ul becomes 470px which is not correct.

HTML:

<div class="box">
<ul class="list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
<li>Item 8</li>
<li>Item 9</li>
<li>Item 10</li>
</ul>
</div>

CSS:

body {
    line-height: 1;
}

/* THIS IS MY CODE*/

.box {
    margin:0 auto;
    width:100%;
    max-width:750px;
    height:53.33vw;
    max-height:400px;
}

ul.list {
    padding: 0;
    width:100%;
    height:100%;
    background:green;
    list-style-type:none;
    text-align:center;
    font-size:3vw;
}

ul li:nth-child(odd) {
    height:8%;
    width:interit;
    padding-top:2%;
    background:grey;
}

ul li:nth-child(even) {
    height:8%;
    width:interit;
    padding-top:2%;
    background:red;
}

Answer

SidOfc picture SidOfc · Jan 5, 2016

This is because padding and margin in % gets calculated from width, not height as you desire.

From the spec for padding

Unlike margin properties, values for padding values cannot be negative. Like margin properties, percentage values for padding properties refer to the width of the generated box's containing block.

And from the spec for margin

The percentage is calculated with respect to the width of the generated box's containing block. Note that this is true for 'margin-top' and 'margin-bottom' as well. If the containing block's width depends on this element, then the resulting layout is undefined in CSS 2.1. This is also how you can create aspect-ratio locked divs in HTML since you can calculate height from width using padding-top with a percentage value.

A solution for you would be to use CSS calc, it has good browser support and fixes your issue in quite a simple manner. The only downside here is that it doesn't calculate the padding-top in % but you simply cannot calculate padding-top in % from the height of the element unless you use javascript.

If you don't absolutely require the padding-top: 2%; I would suggest the below solution.

Your CSS would look like this

ul.list li {
    height: calc(10% - 8px);
    padding-top: 8px;
}

This would give all ul.list li items a height of 10% - 8px and that 8px is then also used as value for padding-top which would basically make a perfect 10% again.

You can write your selectors in a different style too, I used ul.list li here as the main selector to just select all li elements but you have :nth-child(odd) and :nth-child(even) selectors.

You wrote duplicate properties on these selectors while the only difference is their background property.

I would strongly recommend to avoid duplication as much as possible as you'll definitely forget to change both properties sometimes, instead change your CSS to something like this:

/*shared properties in a general selector*/
ul.list li {
    width: inherit;
    height: calc(10% - 8px);
    padding-top: 8px;
    background: grey;
}

/*if rule is not applied, use ul.list instead of just ul*/
ul li:nth-child(even) {
    background: red;
}

This even removes the need for ul li:nth-child(odd) since this can also be part of the shared styling and then be overwritten by the more specific ul li:nth-child(even) selector.

Another way

Another way of doing this instead of relying on % values is to use either em or rem values - they work by scaling based on font size.

More reading

Basically, rem is always relative to the root element which is the <html> element.

Setting a font-size on the <html> element will define the value of 1rem in CSS, if no font-size is set then the browser default (usually 16px will be used).

html {
    font-size: 16px; /*1rem is now 16px*/
}

When you define the font-size like above with a value of 16px then 0.5rem will equal 8px - why is this interesting? It's scaleable! But by different means.

When you now apply a media query and change the font-size to something bigger or smaller, everything using rem will scale proportionally (you'd have to set font-size of the <html> element ofcourse.)

Now em is a bit different, it's relative to it's own font-size but since this get's inherited from it's parent element automatically (if font-size is set in em or % that is) it's basically relative to the parent.

If we take the last CSS example

html {
    font-size: 16px; /* 1rem = 16px, 1em = 16px */
}

body > div { /*every direct div of body tag*/
    font-size: 0.5em; /*8px*/
    font-size: 0.5rem; /*8px*/
}

body > div > div { /*every second level nested div*/
    font-size: 0.5em; /*4px*/
    font-size: 0.5rem; /*8px*/
}

Basically, since em is sort-of relative to it's parent size the font-size decreases more when you scale in em - this is potentially dangerous since if you change your structure and add a container that slightly reduces font-size on most of the page this could have a bigger effect than you think.

With that out of the way, technically em and rem are the best ways to scale padding and margin on elements according to the font-size property.

An example for your container could look like this:

ul.list li {
    height: 8%;
    padding-top: 0.5rem; /*if font-size is 16px, this will be 8px*/
}

In terms of scalability this will work when you change the font-size to something else on a mobile device for instance.

It doesn't provide a solution to your question but it does provide an answer and your best alternative when talking about scalability, I hope this helps you in your quest for that :)