9

Target

enter image description here

<div class="Card">
  <div class="Card-FullNameLabel">Gregg Sims</div>
  <div class="Card-OrganizationNameLabel">Compubotics</div>
</div>
  1. The .Card-FullNameLabel has font-size: 16px and line-height: 1.
  2. The .Card-OrganizationNameLabel has font-size: 12px and line-height: 1.
  3. The vertical space between .Card-FullNameLabel and .Card-OrganizationNameLabel must be exactly 6px.
  4. Below CSS rule must work and must NOT be changed.
.Card-FullNameLabel + .Card-OrganizationNameLabel {
  margin-top: 6px;
}
  1. Both .Card-FullNameLabel and .Card-OrganizationNameLabel must have overflow tolerance (e. g. if this content will be something like ÀÇĤjgpfhjklbĜiEstosTreMalfaci and so on it must not overhang from the parrent).
  2. All letters must be fully visible despite to line-height: 1.
  3. The mental arithmetic (magic numbers and/or hard coded offsets and other values which must be pre-computed) in CSS code are not allowed.

What is O'K to do: use the functionality of Pug pre-processor for markup and CSS pre-processors for styles.

Inital fiddle does not satisfied to the condition number 5: currently the card is not overflow-tolerant.

enter image description here

About line-height: 1, the bad practice

I has been repeatedly told about I must set line-height to value more than 1.

It becomes obvious that setting line-height: 1 is a bad practice. I remind you that unitless values are font-size relative, not content-area relative, and dealing with a virtual-area smaller than the content-area is the origin of many of our problems.

Deep dive CSS: font metrics, line-height and vertical-align

Well, I don't going to dispute about it. All I want is the working solution for the reaching of my target (descripted above). The usage of it is my responsibility and I will not reсcommend this solution if you agree that line-height must be more than 1.

But why I don't want increase the line-height so persistently?

Reason 1: The precise defining of the vertical space between two elements will become too complicated

The rule .Card-FullNameLabel + .Card-OrganizationNameLabel { margin-top: 6px; } is clear, intuitive and expresses the guidelines (represented in the picture above) by CSS. "The .Card-OrganizationNameLabel must retire from .Card-FullNameLabel by 6 pixels", and nothing more.

But what if we need to define the same vertical space between .Card-FullNameLabel and .Card-OrganizationNameLabel when line height is more than 1 (or they have the top and bottom paddings)? The value of the margin-top (visualized by non-overlayed pink area in the picture below) of .Card-FullNameLabel + .Card-OrganizationNameLabel rule now be the difference of:

  1. The desired range (6px)
  2. The extra vertical space below .Card-FullNameLabel (designated as l_b)
  3. The extra vertical space above .Card-OrganizationNameLabel (designated as l_a)

enter image description here

As I told above, the mental arithmetic is not allowed because it devalues the programming (CSS preprocessors capabilities in CSS case) and makes flexibility/maintainability impact (if we change the line-height or font-size or desired vertical space between labels, everything need to be mentally re-computed).

Although the preprocessor's variables (today became available in native CSS) can solve this problem, it will be too complicated to maintain it. To compute the non-intersecting red pink in the image above, we need to:

  1. Variablelize the font-size of .Card-FullNameLabel
  2. Variablelize the line-height of .Card-FullNameLabel
  3. Compute the extra space below .Card-FullNameLabel.
  4. Variablelize the font-size of .Card-OrganizationNameLabel
  5. Variablelize the line-height of .Card-OrganizationNameLabel
  6. Compute the extra space below .Card-OrganizationNameLabel
  7. Variablelize the desired range between .Card-FullNameLabel and .Card-OrganizationNameLabel (6 pixels in this example).

After this, we can finally compute the margin-top for the rule .Card-FullNameLabel + .Card-OrganizationNameLabel. And same for each pair of elements like .Card-FullNameLabel and .Card-OrganizationNameLabel!! Too poor technology for the web development in 2020s.

Reason 2: It does not require for each language

In below example, the Japanese symbols are perfectly fits to line with line-height: 1 (16px):

enter image description here

I suppose same will be for the Chinese, Korean and many other languages with non-latin characters.

But: in the small percentage of cases, there the foreign symbols could be mixed:

enter image description here

If to talk about high quality, this case must be supported.

I don't want increase the line height just for this exception. It's OK that the vertical space between lines actually became not 6px: the tails of j or À has a small weight and it will not break the geometric aesthetics.

My efforts

Attempt 1: usage of :before and :after

The SASS-mixin TextTruncation accepts the parameter $extraSpace which adding top and bottom paddings. The :before and :after pseudo elements compensates this paddings by negative margins.

@mixin TextTruncation($extraSpace, $displayEllipsis: false) {
  
  overflow: hidden;
  white-space: nowrap;
  
  @if ($displayEllipsis) {
    text-overflow: ellipsis;
  } @else {
    text-overflow: clip;
  }
  
  padding-top: $extraSpace;
  padding-bottom: $extraSpace;
  
  &:before,
  &:after {
    content: "";
    display: block;
  }
  
  &:before {
    margin-top: -$extraSpace;
  }
  
  &:after {
    margin-bottom: -$extraSpace;
  }
}

body {
  padding: 12px;
}

* {
  line-height: 1;
  font-family: Arial, sans-serif;
}

.Card {
  
  display: flex;
  flex-direction: column;
  align-items: center;
  
  width: 240px;
  height: 320px;
  padding: 6px 12px 12px;
  
  background: white;
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
}

.Card-FullNameLabel {
  
  max-width: 100%; /* Required when the flex parent has `align-items: center` */
  @include TextTruncation($extraSpace: 2px, $displayEllipsis: true);
  
  font-size: 16px;
  color: #707070;
}

.Card-OrganizationNameLabel {
  
  max-width: 100%; /* Required when the flex parent has `align-items: center` */
  @include TextTruncation($extraSpace: 2px, $displayEllipsis: true);
  
  font-size: 12px;
  color: #A2A2A2;
}

.Card-FullNameLabel + .Card-OrganizationNameLabel {
  margin-top: 6px;
}

Unfortunately, It does not work: the effect is same as if no margins and no paddings has been defined:

enter image description here

CodePen

Attempt 2: usage of the wrapper

If the combination of overflow-x: hidden and overflow-y: visible works, it was the solution. But it does no work and this problem has been considered in the question CSS overflow-x: visible; and overflow-y: hidden; causing scrollbar issue.

I want to avid the wrappers as possible, but here it looks like the wrapper will be the last resort. To avoid of writing two tags each time, I created the Pug mixin:

mixin SingleLineLabel

  span.SingleLineLabel&attributes(attributes)
    span.SingleLineLabel-Text
      block

Well, the SingleLineLabel now a component. Besides the Pug mixin it's required to define the basic styles and SASS mixin allows to customize the label individually:

// Constant styles
.SingleLineLabel {

  overflow-y: visible;

  &:before,
  &:after {
    content: "";
    display: block;
  }


  &-Text {
    display: block;
    overflow-x: hidden;
    white-space: nowrap;
  }
}

// Variable styles
@mixin SingleLineLabel($truncatedVerticalSpaceCompensation, $displayEllipsis: false) {
  
  &:before {
    margin-top: -$truncatedVerticalSpaceCompensation
  }

  &:after {
    margin-bottom: -$truncatedVerticalSpaceCompensation
  }
  

  .SingleLineLabel-Text {
    
    padding-top: $truncatedVerticalSpaceCompensation;
    padding-bottom: $truncatedVerticalSpaceCompensation;
    
    @if ($displayEllipsis) {
      text-overflow: ellipsis;
    } @else {
      text-overflow: clip;
    }
  }
}

Now we can apply it:

.Card-FullNameLabel {
  
  max-width: 100%; /* Required when the flex parent has `align-items: center` */
  @include SingleLineLabel($truncatedVerticalSpaceCompensation: 1px, $displayEllipsis: true);
  
  font-size: 16px;
  color: #707070;
}

.Card-OrganizationNameLabel {
  
  max-width: 100%; /* Required when the flex parent has `align-items: center` */
  @include SingleLineLabel($truncatedVerticalSpaceCompensation: 2px, $displayEllipsis: true);
  
  font-size: 12px;
  color: #A2A2A2;
}

.Card-FullNameLabel + .Card-OrganizationNameLabel {
  margin-top: 6px;
}

It seems like the target has been reached:

enter image description here

CodePen

Unfortunately, it has the bug which occurrence regularity is unclear. Sometimes the small vertical scrollbar appearing.

enter image description here

I really don't know how to reproduce it, but in the past experiment it has occurred, for example, if to switch the browser to device simulation mode by development tools and then exit from this mode. Most likely, you will not get the same effect if repeat same experiment in fiddle.

Finally

The solution based on your great answers will be included to growing @yamato-daiwa/frontend library.

If you have the full list of the problematic symbols like g, p, À, Ĥ and so on, please share it - I'll use it for the tests and also add them to the future pug functionality for the overflow tolerance testing.

Takeshi Tokugawa YD
  • 670
  • 5
  • 40
  • 124
  • Hi, I think I am misunderstanding something - why can't line height be defined as 16px (for example) and font-size something just a bit smaller? Would anyone notice/care? – A Haworth Aug 05 '21 at 18:11
  • @AHaworth, It's simple and obvious - because the font-size become smaller. If designer said "it must be the 16px" it's not allowed to make it 15px and smaller. – Takeshi Tokugawa YD Aug 05 '21 at 23:25
  • 1
    I think you've "painted yourself into a corner" so to speak. Something visually identical to what you're after could be achieved if you were able to be more flexible about certain requirements like `line-height` and `margin` values. – Aaron Sarnat Aug 08 '21 at 08:39
  • 1
    @maqam7, Thank you for the comment. Well, it what I ask is impossible please suggest the alternative close to my requirements. It's good when there are some alternatives to select. – Takeshi Tokugawa YD Aug 08 '21 at 08:45
  • 1
    Coming up shortly :) – Aaron Sarnat Aug 08 '21 at 08:46
  • What if there were a pug/sass solution that used a function to generate something like what I proposed in my answer? Thinking maybe function would take font size and line height of each text box, along with gap height or something like that… then use those values to calculate negative margin offsets. So no magic numbers, just some input values for the preprocessor but same end result? Would have to reverse engineer line-height a bit to figure out how those input values could produce predictable output… – Aaron Sarnat Aug 09 '21 at 03:21
  • Also can it be assumed that each of these truncated divs will always only ever be a single line? There won’t be a case where these ever need to wrap? – Aaron Sarnat Aug 09 '21 at 03:28
  • Another question: Must line-height be set once globally or can it be set on each label class? – Aaron Sarnat Aug 09 '21 at 12:59
  • @maqam7, 1) "What if there were ..." - do you mean the methodology described in "Reason 1" section? 2) "can it be assumed ..." - Yes! The solving of this problem in multiline blocks is pretty easier and not requires the `overflow: hidden`. This question considering single-line elements only. 3) "Must line-height be set" - Afraid no. Assume that the global line-height is 1 and could be customized for each block individually. – Takeshi Tokugawa YD Aug 10 '21 at 01:28
  • @TakeshiTokugawaYD Thanks for the clarification. See my updated answer/comment below. Hope this solves your issue. – Aaron Sarnat Aug 14 '21 at 02:42
  • "clip" instead of "hidden" might solve this. – Michael Tontchev Jan 15 '23 at 08:18

1 Answers1

1

I know you explicitly stated that you needed to keep line-height: 1 and margin-top: 6px, but as you identified with the documented overflow CSS issue, you're kind of stuck with your current restrictions.

If it is at all possible to be flexible about those restrictions, I have a solution that is visually identical to what you were originally after.

Original State

I started with your Initial Fiddle and added ellipsis truncation CSS and problematic text in the html.

.Card-FullNameLabel,
.Card-OrganizationNameLabel {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

The result is this state which I am calling "Original" since it leaves your line-height and margin values unmodified. Note that I have added a single overflow: hidden rule in place of the problematic mixing of overflow-x and overflow-y rules.

Proposed Fix

I propose the following CSS changes. This increases line-height to 1.5 which allows all of the font's ascenders and descenders to be visible. Then I added negative offset margins to compensate:

.Card-FullNameLabel,
.Card-OrganizationNameLabel {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;

  /* Shows all ascenders and descenders */
  line-height: 1.5;
}

.Card-FullNameLabel {
  font-size: 16px;
  color: #707070;

  /* Compensates for line-height */
  margin: -4px 0;
}

.Card-OrganizationNameLabel {
  font-size: 12px;
  color: #A2A2A2;

  /* Compensates for line-height */
  margin: -3px 0;
}

.Card-FullNameLabel + .Card-OrganizationNameLabel {
  /* 6px visually (minus 3px) */
  margin-top: 3px;
}

The result can be seen in action here which I am referring to as "Proposed Fix". I have confirmed the results are consistent in latest desktop Chrome, Firefox, and Safari on MacOS and Mobile Safari on iOS.

Comparison

I made a simple animation from "before" and "after" screenshots that demonstrate that the output is visually identical except that the proposed fix does not cut off the font's ascenders and descenders.

Note that you can click the animation to see a full-size, 1:1 pixel-accurate version.

enter image description here

I did some additional tests with what I'm calling "in-between elements" to demonstrate that the proposed fix would behave the same as the original even if there were elements in-between.

enter image description here


Update: Automation

As was made clear in the comments, one of the requirements is that there be no "hard-coded" or "magic" numbers in the CSS. So while the above solution works, it requires manual arithmetic ahead of time.

Here is an updated Codepen that can automatically produce similar CSS to what was shown above by using some SCSS logic that will calculate the offsets based on these input values:

Variable Current Value
$globalLineHeight 1
$minLineHeight 1.5
$fullNameFontSize 16px
$fullNameLineHeight $globalLineHeight
$orgNameFontSize 12px
$orgNameLineHeight $globalLineHeight
$orgNameMarginTop 6px

For demonstration purposes, I added a bit of extra code that will show a "before" and "after" hover effect so you can see how the SCSS logic behaves compared to the original CSS. As is indicated in both the HTML and CSS, you can delete anything below the lines that begin with #DELETE-ME.

Aaron Sarnat
  • 1,207
  • 8
  • 16
  • I know negative margin offsets are not ideal, but since you stated earlier that you were experimenting with negative margins and `:before` and `:after` pseudo-elements, I surmised that you were placing a premium on accurately portraying the original design. As a designer myself, I try to avoid situations like this whenever possible, but sometimes "ugly" workarounds are necessary to get the job done, especially if design accuracy is a priority. In those cases, I just add some code comments and move on to the next task. – Aaron Sarnat Aug 08 '21 at 09:11
  • Thank you for the answer. I am sorry, but I must evaluate it as jury (same as all other answers). First, you did the mental arithmetic which is not allowed. You have computed the margins of `.Card-FullNameLabel` and `.Card-OrganizationNameLabel` based on `line-height` (converted to `px`) and `font-size` difference and also the `margin` of `.Card-FullNameLabel + .Card-OrganizationNameLabel`. As I told above, it's not acceptable, because it makes the huge maintainability impact. What if designer will change the `font-size` and/or `line-height` and/or vertical space between labels? We need to... – Takeshi Tokugawa YD Aug 09 '21 at 02:36
  • ... re-compute everything! It's a kind of [hard coding](https://en.wikipedia.org/wiki/Hard_coding). The usage of variables in not the solution too (I wrote about it in my question). The verdict: I am sorry to declare that, but your solution does not satisfy to requirement No.7. Do you have the other proposals? – Takeshi Tokugawa YD Aug 09 '21 at 02:40
  • Sorry I misunderstood what you meant by "mental arithmetic" but agree this would introduce a maintainability issue. I guess I'd assumed you were prepared for that given the amount of effort you're putting into trying to uphold the design. Maybe your only other option is to sit down with the designer and explain the technical issues with the current design and get them to sign off on something different that is more maintainable by default? – Aaron Sarnat Aug 09 '21 at 02:41
  • The subject of this question is a technological solution. Well, let's wait for the other answers. It's still 6 day until bounty end. Although you solution is not acceptable, I thank you for the participation! – Takeshi Tokugawa YD Aug 09 '21 at 02:45
  • 2
    For point #7 in your question you might want to change it to something like "no magic numbers or hard coded offset values in the CSS." I'd interpret that statement as what you were going for. – Aaron Sarnat Aug 09 '21 at 02:45
  • @TakeshiTokugawaYD Please see "Update: Automation" that I added to the bottom of my answer. It includes a [Codepen](https://codepen.io/maqam7/pen/JjNxmeY) with a working solution that doesn't require manual calculation of offset values. – Aaron Sarnat Aug 14 '21 at 02:39
  • Thank you for the solution. I am very grateful for you time, but please understand that this solution is not just for me so I can't say "It's great!" only because I thank you and appreciate your help. I really sorry to say this, but I have a feeling you had not read the "Reason 1: The precise defining ..." section and even understand that you must not read it helping for free. Maybe I'm wrong, but it seems to me that your solution is following to approach described in "Reason 1: The precise defining ...". – Takeshi Tokugawa YD Aug 14 '21 at 08:28
  • As as told about this, "Too poor technology (it means too much of routine) for the web development in 2020s.". Please let me to ask: whre your solution coceptually different from the approach described in "Reason 1: The precise defining ..." section? – Takeshi Tokugawa YD Aug 14 '21 at 08:28
  • Apologies. I did read your question, but upon first read I thought you were suggesting using preprocessor logic as a proposed solution to mitigate the "hard-coded values" issue you pointed out earlier. Now I understand that you were eliminating that as an acceptable solution. As I stated earlier, it seems you have "painted yourself into a corner" and left yourself with no real obvious technical solutions that meet all of your strict criteria... – Aaron Sarnat Aug 14 '21 at 21:06
  • I think at this point you either: 1) Discuss alternatives with your designer 2) Accept a less-than-ideal technical solution or 3) Accept the less-than-ideal UX caused by the odd CSS rendering behavior of modern browsers. Personally, I'd choose either 1 or 2. Good luck! – Aaron Sarnat Aug 14 '21 at 21:07
  • If so, I am sorry about false assumptions that you did not read the question. Well, for your time and efforts, I give you the 50 points of reputation prize. Thank you for the participation! – Takeshi Tokugawa YD Aug 15 '21 at 00:32
  • Much appreciated! Sorry we weren’t able to find the ideal solution for you, but this was definitely a good learning exercise. – Aaron Sarnat Aug 15 '21 at 00:37
  • 1
    It's not your fault. It's a CSS imperfection. Hope it will be fixed in the future. – Takeshi Tokugawa YD Aug 15 '21 at 00:51