0

I've found a really strange thing regarding styling absolute positioned input. For some reason, it doesn't follow my CSS provided rules regarding its width.

What I want to achieve is to set the width of absolute positioned input based on the left and right property (see the snippet, input should have width 100% as the div in the second example).

Here some snippet showing my problem.

.wrapper {
  height: 40px;
  width: 200px;
  position: relative;
  background-color: red;
  margin-bottom: 10px;
}

.wrapper > input {
  position: absolute;
  left: 0;
  right: 0;
  outline: 0;
  border: 0;
  top: 0;
  bottom: 0;
}

.inner {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  background-color: blue;
}
<div class="wrapper">
  <input type="text" />
</div>

<div class="wrapper">
  <div class="inner"></div>
</div>

Ok, I think some of you don't understand what's the real problem here. Here is another example of dynamic provided width for absolute positioned input. And please don't suggest the calc(100% - 20px) because this is not the point of the question.

.wrapper {
  height: 40px;
  width: 200px;
  position: relative;
  background-color: red;
  margin-bottom: 10px;
}

.wrapper > input {
  position: absolute;
  left: 20px;
  right: 0;
  outline: 0;
  border: 0;
  top: 0;
  bottom: 0;
}

.inner {
  position: absolute;
  left: 20px;
  right: 0;
  top: 0;
  bottom: 0;
  background-color: blue;
}
<div class="wrapper">
  <input type="text" />
</div>

<div class="wrapper">
  <div class="inner"></div>
</div>

It's just weird input and div having the same CSS applied are parsed so different.

J33nn
  • 3,034
  • 5
  • 30
  • 46

3 Answers3

1

The issue is that an input is not like a div element and they won't behave the same. An input element will by default have styling set by the browser and you will notice that it also have a default width which is creating the issue.

If you refer to the specification or to this previous answer you will have the following formula:

'left' + 'margin-left' + 'width' + 'margin-right' + 'right' = width of containing block

Also a list of rules and in your case the width is never auto.

For your div you will fall into this rule:

  1. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width'

Logically the width will be resolved after setting left and right and you will get the needed result.

For the input you will consider this:

If none of the three is 'auto': If both 'margin-left' and 'margin-right' are 'auto', solve the equation under the extra constraint that the two margins get equal values, unless this would make them negative, in which case when direction of the containing block is 'ltr' ('rtl'), set 'margin-left' ('margin-right') to zero and solve for 'margin-right' ('margin-left'). If one of 'margin-left' or 'margin-right' is 'auto', solve the equation for that value. If the values are over-constrained, ignore the value for 'left' (in case the 'direction' property of the containing block is 'rtl') or 'right' (in case 'direction' is 'ltr') and solve for that value.

A bit complex but in all the cases the width of the input will never change. Here is some basic example:

.box {
  width:300px;
  border:2px solid;
  height:250px;
  position:relative;
}
.box > input {
  border:0;
  background:green;
  position:absolute;
}
.box > input:nth-child(1) {
  left:0;
  right:0;
}
.box > input:nth-child(2) {
  top:50px;
  left:100%;
  right:0;
}
.box > input:nth-child(3) {
  top:100px;
  left:100%;
  right:100%;
}

.box > input:nth-child(4) {
  top:150px;
  left:50px;
  right:50px;
}
.box > input:nth-child(5) {
  top:200px;
  left:80px;
  right:100%;
}
<div class="box">
<input type="text">
<input type="text">
<input type="text">
<input type="text">
<input type="text">
</div>

As you can clearly see, all the width are the same whataver the values of left/right you use and the left is deciding the position.


Your only solution is to override the width on the input element. So instead of left/right you can set left and width with width equal to 100% - L - R where L is the left value and R the value you would have used for right.

.box {
  width: 300px;
  border: 2px solid;
  height: 400px;
  position: relative;
}

.box > input {
  border: 0;
  background: green;
  position:absolute;
}

.box :nth-child(1) {
  left: 0;
  width: 100%;
}

.box :nth-child(2) {
  top: 50px;
  left: 80%;
  width: calc(100% - 80%);
}

.box :nth-child(3) {
  top: 100px;
  left: 100%;
  width: calc(100% - 100% + 50px);
}

.box :nth-child(4) {
  top: 150px;
  left: 50px;
  width: calc(100% - 50px - 50px);
}

.box :nth-child(5) {
  top: 200px;
  left: 80px;
  width: calc(100% - 80px);
}
<div class="box">
  <input type="text">
  <input type="text">
  <input type="text">
  <input type="text">
  <input type="text">
</div>

You can also wrap your element inside another div and make the input width:100% then use left/right on that div:

.box {
  width: 300px;
  border: 2px solid;
  height: 400px;
  position: relative;
}
.box div {
  position:absolute;
}
.box input {
  border: 0;
  background: green;
  width: 100%;
}

.box div:nth-child(1) {
  left: 0;
  right: 0;
}

.box div:nth-child(2) {
  top: 50px;
  left: 80%;
  right: 0;
}

.box div:nth-child(3) {
  top: 100px;
  left: 100%;
  right: -50px;
}

.box div:nth-child(4) {
  top: 150px;
  left: 50px;
  right: 50px;
}

.box div:nth-child(5) {
  top: 200px;
  left: 80px;
  right: 0;
}
<div class="box">
  <div><input type="text"></div>
  <div><input type="text"></div>
  <div><input type="text"></div>
  <div><input type="text"></div>
  <div><input type="text"></div>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
-1

So far I've found the 2 possible solution for this. As @arieljuod suggested, wrapper element with width set with left and right should do the thing.

The second option is to use calc(100% - Xpx). This one will set the valid width of the input.

Still didn't find anything why it's like that and how can we force input to respect those stylings.

J33nn
  • 3,034
  • 5
  • 30
  • 46
-2

You can override how the browser calculates the element width using:

.wrapper > input {
  width: inherit;
}

same thing happens for every element with inline-block as default (like button or textarea)

arieljuod
  • 15,460
  • 2
  • 25
  • 36
  • Doesn't work. It fails for the 2nd snippet (yup I've added this 2mins ago). – J33nn Feb 24 '19 at 18:29
  • 1
    Hmmm, then you'll probably need to add a wrapper div absolute positioned and then the input to be 100% width. I guess the initial width of the input depends on the browser and what it defaults the "size" attribute and with your css you are not setting the width so the browser still sets the width using it's default "size" value. – arieljuod Feb 24 '19 at 18:40
  • The wrapper would do the work indeed, but the main question is why this width is messed up that badly. – J33nn Feb 24 '19 at 18:43
  • That's what I said on the second part of the comment that I think it's actually happening. Since you are not setting the actual `width` property, the browser has a default value for the "size" attribute to calculate it's width. When you define left and right, the width is still unset. Browsers need a default way to calculate the width of elements with `inline-block` as it's default `display` value (for buttons is it's content, for textarea or input it's a default value). For divs, the actual width is calculated as the available space by default. – arieljuod Feb 24 '19 at 19:26
  • But it doesn't happen with the `inline-block` element: https://codepen.io/anon/pen/rRBepv – J33nn Feb 24 '19 at 19:34
  • 1
    @J33nn setting display:inline or any value of display is useless when having position:absolue because in all the case it will be set to a block element – Temani Afif Feb 24 '19 at 20:10
  • @J33nn, I didn't mean elements with `display: inline-block`, I mean elements which its DEFAULT display value is `inline-block`, you used a DIV, DIV's default display value is `block` and set it to `inline-block` which is not the same, so the browser has no predefined width. – arieljuod Feb 24 '19 at 20:44