2

As demonstrated by the first 3 .shrink elements in the included code snippet, I have styled an element such that:

  • when its text is more narrow than its parent: the element is the same width as its text
  • when its text is wider than its parent, the text wraps
    • if the wrapped text fits within the parent, the element is the same width as its text, which is the same width as its parent
    • if the wrapped text is still wider than the parent, the element is the same width as its text, and therefore wider than its parent

My issue is that I would also like to apply min-width: ${n}px to such an element, so that all of the above statements apply, but the final width of the element must be at least the given minimum. This is somewhat demonstrated in the fourth and fifth (last two) .shrink elements in the following example, but as you can observe, the fifth element does not work as intended. As with the fourth .shrink element, the intent is that if its text were less than 90px wide, the box would still be 90px wide, but should also behave like the third .shrink element when the text is wider than the parent. Intuitively, I want to express this as min-width: max(min-content, 90px), but this is not supported. Can this behavior be expressed with css without an additional wrapper element and if so, how?

.shrink {
  width: max-content;
  min-width: min-content;
  max-width: 100%;
  box-sizing: border-box;
}
<div style="display: flex; flex-direction: column; width: 100px; border: 4px solid orange;">
  <div class='shrink' style="border: 4px solid lightblue;">narrow</div>
  <div class='shrink' style="border: 4px solid lightblue;">this text wraps to fit</div>
  <div class='shrink' style="border: 4px solid lightblue;">this text cannot_wrap_any_further</div>
  <div class='shrink' style="border: 4px solid lightblue; min-width: 90px;">min 90px</div>
  <div class='shrink' style="border: 4px solid lightblue; min-width: 90px;">min 90px this_box_should_be_as_wide_as_this_text!!</div>
</div>

A further point of clarification is that the parent should remain a flex column. Making it a wrapped flex row causes other problems, like negating y axis flex expressions (e.g. flex grow on the y axis) and could cause multiple children to be on the same row when they are narrow enough to fit.

m59
  • 43,214
  • 14
  • 119
  • 136
  • Any particular reason why you don't break words to wrap? I'm thinking that maybe you're not aware of the `word-break: break-word` property... – Will Jun 02 '22 at 01:27
  • @Will I'm aware, but it's a separate concern from the one in view of my issue. What I have here is applicable whether text wrapping is disabled, maximized, or anything between. This issue at hand concerns how the element is sized based on its content. Expressions of how the content may break and wrap are a separate concern. – m59 Jun 02 '22 at 02:16
  • Sure, I was just trying to determine if your entire issue could be fixed with just that one CSS property. If that's not an option, then can the HTML be edited? I'll try to take a look tonight. – Will Jun 02 '22 at 02:18
  • The reason I'm interested in handling it without an additional element is that I'm writing a component library, and all other features can be implemented with the minimal infrastructure I have already. Checking for this case to handle it specially with an additional dom element will have performance costs and will make for significantly uglier code. I'd just document the unfortunate caveat that `shrink` with `minimum` doesn't work right in this case. – m59 Jun 02 '22 at 03:55

2 Answers2

1

If you are open to some "hack", I would consider a pseudo element that will force the min-width to 90px

.shrink {
  width: max-content;
  min-width: min-content;
  max-width: 100%;
  box-sizing: border-box;
}

.shrink:after {
  content: "";
  display: block;
  width: 82px; /* you have to consider the border of the main element */
}
<div style="width: 100px; border: 4px solid orange;">
  <div class='shrink' style="border: 4px solid lightblue;">narrow</div>
  <div class='shrink' style="border: 4px solid lightblue;">this text wraps to fit</div>
  <div class='shrink' style="border: 4px solid lightblue;">this text cannot_wrap_any_further</div>
  <div class='shrink' style="border: 4px solid lightblue;">min 90px</div>
  <div class='shrink' style="border: 4px solid lightblue;">min 90px this_box_should_be_as_wide_as_this_text!!</div>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • I think OP doesn't want all `shrink` elements to have a minimum width of 90px. So maybe add a class for those elements, like so: https://jsfiddle.net/4ko8b53t/ – Will Jun 02 '22 at 10:41
  • Great job! This may be the best/only choice. In my real life use case, the minimum width and border are dynamic and this is all calculated by JS, so it will be painful using the pseudo element, but it should be tolerable. – m59 Jun 02 '22 at 14:34
  • 1
    @m59 you can use a CSS variables for the width inside the pseudo element and you can easily update that variable using JS. Related: https://stackoverflow.com/a/49618941/8620333 – Temani Afif Jun 02 '22 at 14:36
  • If I only apply the class when needed and always apply the variable to an element that has the class, that seems right. I was thinking about the parent variable applying to children, but that would only be if I either always applied the class, and/or didn't always supply the variables. – m59 Jun 02 '22 at 15:17
  • The issue with using a pseudo element is that it affects children elements, so this only works if the content of the element is text. While the answer by @Will conflicts with having a flex column, it does work whether the child is text or elements. – m59 Jun 02 '22 at 18:01
0

You can use flexbox and simply change min-width for flex-basis.

Note that you can drop the shrink class altogether and style the children elements with .parent > div in your CSS. I kept the class in the solution for clarity, but this is what it would look like: https://jsfiddle.net/f2unybok/

.parent {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
}
.shrink {
  box-sizing: border-box;
}
<div class='parent' style="width: 100px; border: 4px solid orange;">
  <div class='shrink' style="border: 4px solid lightblue;">narrow</div>
  <div class='shrink' style="border: 4px solid lightblue;">this text wraps to fit</div>
  <div class='shrink' style="border: 4px solid lightblue;">this text cannot_wrap_any_further</div>
  <div class='shrink' style="border: 4px solid lightblue; flex-basis: 90px;">min 90px</div>
  <div class='shrink' style="border: 4px solid lightblue; flex-basis: 90px;">min 90px this_box_should_be_as_wide_as_this_text!!</div>
</div>
Will
  • 1,123
  • 1
  • 9
  • 22
  • I now realize I left off a relevant detail, which I'll edit into my post. I need this to work on the cross axis in the same way. The parent may have `flex-direction: column`, with the children using flex-grow and flex-shrink on the y axis. So now that I think about it, another way of stating the concern is getting this flex behavior on the y axis and x axis at the same time. – m59 Jun 02 '22 at 13:48