20

I have several divs that are all styled as inline-blocks, and therefore sit in a row next to each other. These divs have a max-width of 140px, and contain text that cause them to vary in width up to that size.

As demonstrated in this fiddle, text that is wider than the max-width property wraps, as you would expect. The problem is that it also seems to force its container div to stay at the max-width, even though the wrapped text doesn't technically require that much space.

Is there a cross-browser, HTML/CSS-only way to "shrink-wrap" these blocks to their smallest width once the text wraps? I'm aware that I can force it with appropriately placed <br>s, but these blocks are supposed to contain user-entered text.

.block {
    display: inline-block;
    vertical-align: top;
    max-width: 140px;
    background-color: lightgrey;
    text-align: center;
}
<div class="block">A single line</div>
<div class="block">Two distinctively different lines</div>
<div class="block">A somewhat egregious demonstrative amount of text</div>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
n18l
  • 375
  • 1
  • 3
  • 6

4 Answers4

13

@BoltClock explains the issue well at https://stackoverflow.com/a/12377883/3903374

Your only option is JavaScript.

You can do so by shrinking each div's width until its height changes:

var d = document.querySelectorAll('.block'),
    i, w, width, height;

for(i = 0; i < d.length; i++) {
  width = d[i].offsetWidth;
  height = d[i].offsetHeight;

  for (w = width; w; w--) {
    d[i].style.width = w + 'px';
    if (d[i].offsetHeight !== height) break;
  }
  
  if (w < d[i].scrollWidth) {
    d[i].style.width = d[i].style.maxWidth = d[i].scrollWidth + 'px';
  } else {
    d[i].style.width = (w + 1) + 'px';
  }
}
.block {
  display: inline-block;
  vertical-align: top;
  max-width: 140px;
  background-color: lightgrey;
  text-align: center;
}
<div class="block">A single line</div>
<div class="block">Two distinctively different lines</div>
<div class="block">A somewhat egregious demonstrative amount of text</div>
<div class="block">LongTextThatsWiderThanMaxWidth Loremipsumdolorsitamet,consecteturadipiscingelit</div>
Rick Hitchcock
  • 35,202
  • 5
  • 48
  • 79
  • The question asks for CSS only, but this is still a great overall solution +1. You check for changes in height, this guy (cited in my answer) [measures the length of each word](http://stackoverflow.com/a/14597951/3597276). LOL .. both seem to work. – Michael Benjamin Oct 20 '15 at 20:57
  • 2
    Wow, that seems a bit overkill, but whatever works : ) – Rick Hitchcock Oct 20 '15 at 21:05
  • 1
    Great find on the question you referenced; I searched SO for an hour and never managed to come across that one. Given that there isn't really a legitimate answer within my scope, I appreciate both of you giving viable JS solutions instead! – n18l Oct 20 '15 at 21:31
  • I implemented the above solution and it works great ... unless there is a word which alone won't fit in the `max-width` size. I tried to add some word-wrap/overflow-wrap properties but no success. Any suggestion ? – karlitos Sep 03 '18 at 20:46
  • 1
    @karlitos, I assume max-width should be ignored in that case. If so, see my update. – Rick Hitchcock Sep 03 '18 at 22:08
  • @RickHitchcock Actually using `word-break: break-word;` and `overflow-wrap: break-word;` helped a lot, but I will gladly test you proposed solution. Many thanks for the update. – karlitos Sep 04 '18 at 11:52
8

Instead of max-width: 140px, consider width: min-content.

CSS

.block {
    display: inline-block;
    vertical-align: top;
    /* max-width: 140px; */
    background-color: lightgrey;
    text-align: center;

    /* new */
    width: min-content;
    padding: 0 5px; 
}

DEMO: https://jsfiddle.net/ex4n8m49/2/

Note: Before implementing min-content check prefixing requirements and browser support.


Alternative Solution

I'm not sure there's a CSS only solution, as you are requesting. In case you decide to try JavaScript, the accepted answer to the following post may help you:

Community
  • 1
  • 1
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • Unfortunately, browser support isn't great. http://caniuse.com/#search=min-content – Brian Glaz Oct 20 '15 at 19:04
  • 2
    Very interesting—I had never even heard of that property until you mentioned it! The only problem (other than browser support, since I'm shooting for at least some level of IE compatibility) is that I still want to operate with a `max-width`-like structure, where the text isn't forced to wrap if it doesn't have to. – n18l Oct 20 '15 at 20:15
  • See my answer here for a more detailed explanation: http://stackoverflow.com/q/37406353/3597276 – Michael Benjamin Feb 07 '17 at 15:48
3

You will need some JavaScript, as others have noted. Create a Range object to measure your content widths. Then set the width of each block accordingly.

const range = document.createRange();
const blocks = document.querySelectorAll('.block');

for (const el of blocks) {
  const text = el.childNodes[0];
  range.setStartBefore(text);
  range.setEndAfter(text);

  const clientRect = range.getBoundingClientRect();
  el.style.width = `${clientRect.width}px`;
}
.block {
    display: inline-block;
    vertical-align: top;
    max-width: 140px;
    background-color: lightgrey;
    text-align: center;
}

.bad {
    display: inline-block;
    vertical-align: top;
    max-width: 140px;
    background-color: pink;
    text-align: center;
}
<div class="block">A single line</div>
<div class="block">Two distinctively different lines</div>
<div class="block">A somewhat egregious demonstrative amount of text</div>

<br /><br />
<div class="bad">A single line</div>
<div class="bad">Two distinctively different lines</div>
<div class="bad">A somewhat egregious demonstrative amount of text</div>
mfluehr
  • 2,832
  • 2
  • 23
  • 31
  • As JavaScript solutions for this issue go, this is very slick and an excellent use of [the Range API](https://developer.mozilla.org/en-US/docs/Web/API/Range). Thanks for sharing! – n18l Aug 10 '22 at 21:39
-2

You can just set overflow-wrap: anywhere; or one of the other values (like break-word) and it should work!

Adam LaMorre
  • 655
  • 7
  • 21