'Using calc to indent child element of CSS Grid accurately align with grid columns

I have a content block within a CSS Grid item, which works fine. In some instances I want to be able to indent a child element 1 or 2 columns so accurately so it aligns to its parent grid.

I did achieve that by applying to same grid to the child block (storyblock__caption in example) however that led to elements spanning 100% wide of the available space, which I didn't want. So I decided to try and tackle it using calc.

I've included an example that at a glance actually looks correct. The paragraph in the example using calc appears to align to the 2nd column of the grid. However if you were to change the spacing so it aligned to the left edge of the 3rd column, something like: margin-left: calc(((2 / 4) * 100%) - ((2 / 4) * 12px)); you can see the math doesn't add up.

I believe this is could be because the grid isn't taking into account the 12px left/right padding of storyblock__caption. But I've tried to add/subtract 24px from the calculation but I'm having trouble getting it to work.

Here's my example code as it stands:

/* #BASE */

body  {color: white; margin: 0 auto;}
img   {width: 100%; max-width: 100%;}
h1    {margin-bottom: 15px;}
p     {margin-bottom: 15px;}
.btn  {background: white; color: black; display: inline-block; padding: 8px 24px; text-decoration: none;}

/* #CONTENT BLOCK */

.storyblock {
  display: grid;
  grid-column-gap: 12px;
  grid-template-columns: repeat(4, 1fr);
  margin: 0 auto;
  position: relative;
  max-width: 375px;
}

.storyblock--overlay {
  align-items: end;
}

.storyblock--overlay .storyblock__media,
.storyblock--overlay .storyblock__caption {
  grid-row: 1;
}

.storyblock__media,
.storyblock__caption {
  grid-column: 1/5;
}

.storyblock__caption {
  padding: 0 12px 24px;
}

.storyblock__caption p {
  margin-left: calc(((1 / 4) * 100%) - ((1 / 4) * 12px));
}
<div class="storyblock storyblock--overlay">
  <div class="storyblock__media">
    <img src="https://www.fillmurray.com/375/500" alt="ALT TEXT" />
  </div>
  <div class="storyblock__caption">
    <h1>Lorem Ipsum</h1>
    <p>Lorem ipsum dolor sit amet consectetur adipiscing.</p>
    <a href=" #" class="btn">Shop Now</a>
  </div>
</div>


Solution 1:[1]

Use CSS variables and make your life easier. I have changed the gap to 10px to better identify the valuee and avoid the confusion with the padding that has the same value. You can also use CSS variables for the padding and the gap

/* #BASE */

body  {color: white; margin: 0 auto;}
img   {width: 100%; max-width: 100%;}
h1    {margin-bottom: 15px;}
p     {margin-bottom: 15px;}
.btn  {background: white; color: black; display: inline-block; padding: 8px 24px; text-decoration: none;}

/* #CONTENT BLOCK */

.storyblock {
  display: grid;
  grid-column-gap: 10px;
  grid-template-columns: repeat(4, 1fr);
  margin: 0 auto;
  position: relative;
  max-width: 375px;
}

.storyblock--overlay {
  align-items: end;
}

.storyblock--overlay .storyblock__media,
.storyblock--overlay .storyblock__caption {
  grid-row: 1;
}

.storyblock__media,
.storyblock__caption {
  grid-column: 1/5;
}

.storyblock__caption {
  padding: 0 12px 24px;
}

.storyblock__caption p.indent {
  margin-left: calc(var(--n)*(100% + 2*12px + 10px)/4 - 12px);
}
<div class="storyblock storyblock--overlay">
  <div class="storyblock__media">
    <img src="https://www.fillmurray.com/375/500" alt="ALT TEXT" />
  </div>
  <div class="storyblock__caption">
    <h1>Default</h1>
    <p>Lorem ipsum dolor sit amet consectetur adipiscing.</p>
    <a href=" #" class="btn">Shop Now</a>
  </div>
</div>


<div class="storyblock storyblock--overlay">
  <div class="storyblock__media">
    <img src="https://www.fillmurray.com/375/500" alt="ALT TEXT" />
  </div>
  <div class="storyblock__caption">
    <h1>1 Col Indent</h1>
    <p class="indent" style="--n:1">Lorem ipsum dolor sit amet consectetur adipiscing.</p>
    <a href=" #" class="btn">Shop Now</a>
  </div>
</div>

<div class="storyblock storyblock--overlay">
  <div class="storyblock__media">
    <img src="https://www.fillmurray.com/375/500" alt="ALT TEXT" />
  </div>
  <div class="storyblock__caption">
    <h1>2 Col Indent</h1>
    <p class="indent" style="--n:2">Lorem ipsum dolor sit amet consectetur adipiscing.</p>
    <a href=" #" class="btn">Shop Now</a>
  </div>
</div>

<div class="storyblock storyblock--overlay">
  <div class="storyblock__media">
    <img src="https://www.fillmurray.com/375/500" alt="ALT TEXT" />
  </div>
  <div class="storyblock__caption">
    <h1>3 Col Indent</h1>
    <p class="indent" style="--n:3">Lorem ipsum dolor sit amet consectetur adipiscing.</p>
    <a href=" #" class="btn">Shop Now</a>
  </div>
</div>

Solution 2:[2]

I'd appreciate a sanity check on it but I think I have resolved this by using the following calculation to indent by 1 column and align with parent grid gutters...

1 col: margin-left: calc(((1 / 4) * (100% + 24px)) - ((3 / 4) * 12px));

Then if you wanted to indent by 2 columns you'd just change the fractions to:

2 cols: margin-left: calc(((2 / 4) * (100% + 24px)) - ((2 / 4) * 12px));

And finally 3 columns. Which looks silly but just to complete the set...

3 cols: margin-left: calc(((3 / 4) * (100% + 24px)) - ((1 / 4) * 12px));

...in future I might just set the same grid on the child container and wrap the items I don't want to stretch 100% wide in a div!

/* #BASE */

body  {color: white; margin: 0 auto;}
img   {width: 100%; max-width: 100%;}
h1    {margin-bottom: 15px;}
p     {margin-bottom: 15px;}
.btn  {background: white; color: black; display: inline-block; padding: 8px 24px; text-decoration: none;}

/* #CONTENT BLOCK */

.storyblock {
  display: grid;
  grid-column-gap: 12px;
  grid-template-columns: repeat(4, 1fr);
  margin: 0 auto;
  position: relative;
  max-width: 375px;
}

.storyblock--overlay {
  align-items: end;
}

.storyblock--overlay .storyblock__media,
.storyblock--overlay .storyblock__caption {
  grid-row: 1;
}

.storyblock__media,
.storyblock__caption {
  grid-column: 1/5;
}

.storyblock__caption {
  padding: 0 12px 24px;
}

.storyblock__caption p.indent-1 {
  margin-left: calc(((1 / 4) * (100% + 24px)) - ((3 / 4) * 12px));
}

.storyblock__caption p.indent-2 {
  margin-left: calc(((2 / 4) * (100% + 24px)) - ((2 / 4) * 12px));
}

.storyblock__caption p.indent-3 {
  margin-left: calc(((3 / 4) * (100% + 24px)) - ((1 / 4) * 12px));
}
<div class="storyblock storyblock--overlay">
  <div class="storyblock__media">
    <img src="https://www.fillmurray.com/375/500" alt="ALT TEXT" />
  </div>
  <div class="storyblock__caption">
    <h1>Default</h1>
    <p>Lorem ipsum dolor sit amet consectetur adipiscing.</p>
    <a href=" #" class="btn">Shop Now</a>
  </div>
</div>


<div class="storyblock storyblock--overlay">
  <div class="storyblock__media">
    <img src="https://www.fillmurray.com/375/500" alt="ALT TEXT" />
  </div>
  <div class="storyblock__caption">
    <h1>1 Col Indent</h1>
    <p class="indent-1">Lorem ipsum dolor sit amet consectetur adipiscing.</p>
    <a href=" #" class="btn">Shop Now</a>
  </div>
</div>

<div class="storyblock storyblock--overlay">
  <div class="storyblock__media">
    <img src="https://www.fillmurray.com/375/500" alt="ALT TEXT" />
  </div>
  <div class="storyblock__caption">
    <h1>2 Col Indent</h1>
    <p class="indent-2">Lorem ipsum dolor sit amet consectetur adipiscing.</p>
    <a href=" #" class="btn">Shop Now</a>
  </div>
</div>

<div class="storyblock storyblock--overlay">
  <div class="storyblock__media">
    <img src="https://www.fillmurray.com/375/500" alt="ALT TEXT" />
  </div>
  <div class="storyblock__caption">
    <h1>3 Col Indent</h1>
    <p class="indent-3">Lorem ipsum dolor sit amet consectetur adipiscing.</p>
    <a href=" #" class="btn">Shop Now</a>
  </div>
</div>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 user1406440