5

I have a piece of html/css that represents a button with a border.

The button has pseudo elements which overlay the button - the simple example shows one of them.

The pseudo element is taller than the original element (height set using px) but the same width (set at 100%).

There are two issues which aren't working as I expect in the current design:

  1. Despite using box-sizing: border-box, the pseudo width does not include the border.
  2. The pseudo element is positioned absolutely (top, left) but this reference position is inside the parent border.

This seems to be the same in both Chrome and Edge which would indicate I'm not doing something right - however, I'm particularly confused with regard to box-sizing.

.container {
  padding: 50px;
}

.button {
  border: solid 4px red;
  box-sizing: border-box;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  height: 36px;
  padding: 0 16px;
  position: relative;
  z-index: 1;
}

.button::before {
  background-color: rgba(76, 255, 0, 0.8);
  box-sizing: inherit;
  content: "";
  display: block;
  position: absolute;
  top: -4px;
  left: 0;
  height: 44px;
  width: 100%;
  z-index: -1;
}
<div class="container">
  <a class="button">Button</a>
</div>
John Ohara
  • 2,821
  • 3
  • 28
  • 54
  • 2
    which is logical, the border is on the parent not the pseudo element, box-sizing has nothing to do there .. and yes the reference is the padding-box, not the border-box – Temani Afif Jan 27 '19 at 15:19
  • The position is inside its parent because the parent has `position: relative`. https://jsbin.com/dupubutofa/edit?html,css,output – Mosh Feu Jan 27 '19 at 15:23
  • @TemaniAfif - I'm not convinced that the box-sizing is logical. Let's say for example, the parent width is 100px including 8px border (4px x 2), I would expect the pseudo to have a width of 100px too. – John Ohara Jan 27 '19 at 15:43
  • no, the element will have the width of the parent minus the border – Temani Afif Jan 27 '19 at 15:46

3 Answers3

7

From the specification

The position and size of an element's box(es) are sometimes calculated relative to a certain rectangle, called the containing block of the element. The containing block of an element is defined as follows:

....

  1. If the element has 'position: absolute', the containing block is established by the nearest ancestor with a 'position' of 'absolute', 'relative' or 'fixed', in the following way:
    1. In the case that the ancestor is an inline element, the containing block is the bounding box around the padding boxes of the first and the last inline boxes generated for that element. In CSS 2.1, if the inline element is split across multiple lines, the containing block is undefined.
    2. Otherwise, the containing block is formed by the padding edge of the ancestor

Then

The padding edge surrounds the box padding. If the padding has 0 width, the padding edge is the same as the content edge. The four padding edges define the box's padding box.

This explain why your element doesn't use the border-box as reference but the padding-box when positionned. It's also the same for percentage width1. using width:100% means the padding and the content of the containing block. Border aren't counted.


Concerning box-sizing

... , any padding or border specified on the element is laid out and drawn inside this specified width and height.

So the border need to belong to the element not a parent element in order to consider box-sizing which is not your case since the border isn't applied to the pseudo element:


1 For absolutely positioned elements whose containing block is based on a block container element, the percentage is calculated with respect to the width of the padding box of that element.ref

.box {
  border:5px solid;
  padding:10px;
  background:red;
  min-height:100px;
  position:relative;
}
span:first-child {
  display:inline-block;
  width:100%;
  background:blue;
}
span:last-child {
  position:absolute;
  bottom:0;
  left:0;
  width:100%;
  background:green;
}
<div class="box">
  <span>I am a static element</span>
  <span>I am a absolute element</span>
</div>

An idea to obtain what you want is to use inset box-shadow instead of border:

.container {
  padding: 50px;
}

.button {
  box-shadow: inset 0 0 0 4px red;
  box-sizing: border-box;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  height: 36px;
  padding: 0 16px;
  position: relative;
  z-index: 1;
}
.button::before {
  background-color: rgba(76, 255, 0, 0.8);
  box-sizing: inherit;
  content: "";
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  z-index: -1;
}
<div class="container">
    <a class="button">Button</a>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
0

Try increasing the width of the pseudoelement with the size of the border of the parent and shift it to the left with left: -4px:

.container {
  padding: 50px;
}

.button {
  border: solid 4px red;
  box-sizing: border-box;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  height: 36px;
  padding: 0 16px;
  position: relative;
  z-index: 1;
}

.button::before {
  background-color: rgba(76, 255, 0, 0.8);
  box-sizing: inherit;
  content: "";
  display: block;
  position: absolute;
  top: -4px;
  left: -4px;
  height: 44px;
  width: calc(100% + 8px);
  z-index: -1;
}
<div class="container">
  <a class="button">Button</a>
</div>
Laurens
  • 2,596
  • 11
  • 21
  • border-box tells the browser to account for any border and padding in the values you specify for an element's width and height. https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing – Temani Afif Jan 27 '19 at 15:23
  • I don't know why this answer has been marked down - if what Temani Afif says is right, then the only solution is to fix it using a method such as this. – John Ohara Jan 27 '19 at 15:44
0

Im 3 years late but Ive found a solution to this. Credits to https://www.getpeblo.com I saw this implemented on their site first. Also thanks to user Temani Afif for clearing it up that the absolutely positioned pseudo element will get the width of its parent's padding box and not border box.

So to get around this you need to wrap the button in which you have set a border in a div container. This div has to have a display of inline block so its width will be the full width of the button(Note that if your container is a grid or flex child of a grid/flex container I think it sort of behaves like an inline block but id still suggest to set the display to inline-block anyway).

Now you want the pseudo element to be a descendant of the container and not the button itself so it gets the full width of the button as the container's padding box is the same width as the button's border box. Here's the snippet:

/* UNRELATED STYLES*/
*{
  margin: 0;
  padding: 0;
  border:0;
  box-sizing: border-box;
}

*::after{
  box-sizing:border-box;
}

body{
  font-family: "Helvetica", "sans-serif";
  line-height: 1;
  font-weight:400;
}

.container{
  max-width:900px;
  min-height:100vh;
  margin: 0 auto;
  display:grid;
  place-items:center;
}

/* Fix */

.btn-contain{
  position:relative;
  display:inline-block;
}

.btn-contain::after{
  content: "";
  position: absolute;
  top:2px;
  left:2px;
  height: 100%;
  width: 100%;
  border-radius: 4px;
  background-color: #34344B;
  z-index: -1;
}

.btn:any-link{
  display: inline-block;
  text-decoration: none;
  padding: 12px 30px;
  border: 2px solid #34344B;
  border-radius: 4px;
  font-size: 18px;
  font-weight: 600;
  background-color: #F031A7;
  color: #34344B;
  transition: 300ms;
}
<div class="container">
  <div class="btn-contain">
    <a class="btn" href="#">Im a button</a>
  </div>  
</div>
herion05
  • 13
  • 3