CSS dot leaders at first line over complex background

Viewed 455

I need to implement dot leaders after first line of text using CSS to use it over texture/image background.

Expected behaviour:

enter image description here

I saw a few implementation of dot leaders over the internet. They all use one of the techniques below:

  1. ::after dots + overflow
<div class=item1>
  <span class=text1>
    Text
  </span>
  <span class=price1>
    $100
  </span>
</div>
.item1 {
  display: flex;
}
.text1 {
  flex-grow: 1;
  position: relative;
  overflow: hidden;
}
.text1::after {
  content: '';
  position: absolute;
  bottom: 0;
  border-bottom: 1px dotted grey;
  width: 100%;
}
.price1 {
  align-self: flex-end;
}

  1. absolute dots + white background
<div class=item2>
  <span class=text2>
    <span class=bg2>
      Text
    </span>
  </span>
  <span class=price2>
    <span class=bg2>
      $100
    </span>
  </span>
</div>
.item2 {
  display: flex;
  justify-content: space-between;
  position: relative;
  mix-blend-mode: darken;
}
.item2::before {
  content: '';
  position: absolute;
  top: 1em;
  left: 0;
  right: 0;
  border-bottom: 1px grey dotted;
}
.bg2 {
  background: white;
  position: relative;
  z-index: 1;
}
  1. dumb flexbox
<div class=item3>
  <span class=text3>
    Text
  </span>
  <span class=dots3></span>
  <span class=price3>
    $100
  </span>
</div>
.item3 {
  display: flex;
  align-items: flex-start;
}
.dots3 {
  flex-grow: 1;
  border-bottom: 1px dotted grey;
  min-width: 10px;
  height: 1em;
}

They are all shown here: https://jsfiddle.net/rzmLg4yu/62/

Each of these techniques has its own pitfalls in my case.

  1. Has dot leaders at last line.
  2. Does not work over complex background, even with mix-blend-mode (uncomment bg line to see). enter image description here
  3. Has large white gaps due to line breaks (resize to see). Switching to grid is no use. enter image description here

Is there more solutions to this case?

5 Answers

Technic 3 can be improved to get rid of the wrapping gaps:

body {
  color: grey;
}

.width {
  width: 300px;
  padding: 5px;
  resize: horizontal;
  overflow: hidden;
  abackground: linear-gradient(45deg, yellow, black);
  background-repeat: repeat-x;
  background-size: 200px 100%;
}

.item3 {
  display: flex;
  align-items: flex-start;
}

.dots3 {
  flex-grow: 1;
  border-bottom: 1px dotted grey;
  min-width: 10px;
  height: 1em;
}

.text3 {
  padding-right: 0px;
  text-align: justify; /* <--- */
}
<div class=width>

  <div class=item3>
    <span class=text3>
        3 Text text
      </span>
    <span class=dots3></span>
    <span class=price3>
        $100
      </span>
  </div>
  <div class=item3>
    <span class=text3>3 Text text text text text text text text text text text text text text text text</span>
    <span class=dots3></span>
    <span class=price3>
        $100
      </span>
  </div>
Here text-align: justify; will space the words evenly.


I think your issue with the second technic is because of white background color right?
because of .bg2 {background: white;...}?
Otherwise, you should've used mix-blend-mode: lighten; to demonstrate the issue:

body {
  color: grey;
  padding: 20px;
}

.width {
  width: 230px;
  padding: 5px;
  resize: horizontal;
  overflow: auto;
  background: linear-gradient(90deg, yellow, black);
  background-repeat: repeat-x;
  background-size: 200px 100%;
}

div>div {
  background-color: white;
  border: 2px solid green;
}

.simpleDiv1 {
  mix-blend-mode: darken;
}

.simpleDiv2 {
  margin-top: 20px;
  mix-blend-mode: lighten;
}
<div class=width>
  <div class=simpleDiv1>darken blend. Simple text without any styles and stuff.</div>
  <div class=simpleDiv2>lighten blend. Simple text without any styles and stuff.</div>
</div>

If you use darken blend then any normal text will disappear with that background, let alone dot leaders.


Technic two without background white:

body {
  color: grey;
  padding: 20px;
}

.width {
  width: 230px;
  padding: 5px;
  resize: horizontal;
  overflow: hidden;
  abackground: linear-gradient(90deg, yellow, black);
  background-repeat: repeat-x;
  background-size: 200px 100%;
}

.item2 {
  display: flex;
  justify-content: space-between;
  position: relative;
  mix-blend-mode: darken;
  text-decoration-line: underline;
  text-decoration-style: solid;
  text-decoration-color: white;
  text-decoration-thickness: 2px;
}

.item2::before {
  content: '\00a0';
  height: calc(1em - 0.5px);
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  border-bottom: 1px dotted gray;
  z-index: -1;
}
<div class=width>

  <div class=item2>
    <span class=text2>
        2 Text text
      </span>
    <span class=price2>
        $100
      </span>
  </div>
  <div class=item2>
    <span class=text2>
        2 Text text text text text text text text text text text text text text text text
      </span>
    <span class=price2>
        $100
      </span>
  </div>
</div>

Here I'm using white underline, instead of whole white background, to hide dotleader. But you'll get white underline while using some blend modes.


Technic three can be improved if we are willing to break words while wrapping:

body {
  color: grey;
}

.width {
  width: 300px;
  padding: 5px;
  resize: horizontal;
  overflow: hidden;
  abackground: linear-gradient(45deg, yellow, black);
  background-repeat: repeat-x;
  background-size: 200px 100%;
}

.item3 {
  display: flex;
  align-items: flex-start;
}

.dots3 {
  flex-grow: 1;
  border-bottom: 1px dotted grey;
  min-width: 10px;
  height: 1em;
}

.text3 {
  padding-right: 0px;
  word-break: break-all;
}
<div class=width>

  <div class=item3>
    <span class=text3>
        3 Text text
      </span>
    <span class=dots3></span>
    <span class=price3>
        $100
      </span>
  </div>
  <div class=item3>
    <span class=text3>3 Text text text text text text text text text text text text text text text text</span>
    <span class=dots3></span>
    <span class=price3>
        $100
      </span>
  </div>

Here instead of wrapping on word boundaries, I'm wrapping on letter boundaries using word-break: break-all. If your font-size is 1em, then this way the maximum gap will be less than 1em, because browser won't be able to fit a letter less than 1em space.

Three improvements for the second technique

  1. I suggest using the value multiply for the mix-blend-mode property. In the case of dark text, it gives a sharper result even on a gradient background. Look the first demo.

  2. We can simplify layout of the second technique with a help of the ::first-line pseudo-element. It can be used with "only a small subset of CSS properties", but this subset contains all background-related properties.
    I think this also gives a better result against mix-blend-mode: darken. Compare the second and third demo.

  3. And the gap property helps to set the minimum gap between the text and the price.

There are three demos in the snippet below:

  • First one shows mix-blend-mode: multiply in action.
  • The other two compare the effect of mix-blend-mode: darken on two layouts - with an extra bg block and with the ::first-line pseudo-element.

https://codepen.io/glebkema/pen/NWapXPQ?editors=1100

body {
  color: grey;
}

.width {
  width: 300px;
  padding: 5px;
  resize: horizontal;
  overflow: hidden;
  background: linear-gradient(45deg, yellow, black);
  background-repeat: repeat-x;
  background-size: 200px 100%;
}

.item2 {
  display: flex;
  justify-content: space-between;
  position: relative;
  mix-blend-mode: darken;
  gap: 10px;                 /* 3) */
}

.multiply .item2 {
  mix-blend-mode: multiply;  /* 1) */
}

.item2::before {
  content: '';
  position: absolute;
  top: 1em;
  left: 0;
  right: 0;
  border-bottom: 1px grey dotted;
}

.bg2 {
  background: white;
  position: relative;
  z-index: 1;
}


.price4,
.text4 {
  z-index: 1;
}

.price4,
.text4::first-line {         /* 2) */
  background: white;
}
<h4><code>mix-blend-mode: multiply;</code></h4>
<div class="width multiply">
    <div class=item2>
        <span class=text2>
            <span class=bg2>
                2 Text text
            </span>
        </span>
        <span class=price2>
            <span class=bg2>
                $100
            </span>
        </span>
    </div>
    <div class=item2>
        <span class=text2>
            <span class=bg2>
                2 Text text text text text text text text text text text text text text text text text
            </span>
        </span>
        <span class=price2>
            <span class=bg2>
                $100
            </span>
        </span>
    </div>

    <br />

    <div class=item2>
        <span class=text4>
            4 Text text
        </span>
        <span class=price4>
            $100
        </span>
    </div>
    <div class=item2>
        <span class=text4>
            4 Text text text text text text text text text text text text text text text text text
        </span>
        <span class=price4>
            $100
        </span>
    </div>
</div>

<h4><code>mix-blend-mode: darken;</code> (2nd method before 4th)</h4>
<div class="width">
    <div class=item2>
        <span class=text2>
            <span class=bg2>
                2 Text text
            </span>
        </span>
        <span class=price2>
            <span class=bg2>
                $100
            </span>
        </span>
    </div>
    <div class=item2>
        <span class=text2>
            <span class=bg2>
                2 Text text text text text text text text text text text text text text text text text
            </span>
        </span>
        <span class=price2>
            <span class=bg2>
                $100
            </span>
        </span>
    </div>

    <br />

    <div class=item2>
        <span class=text4>
            4 Text text
        </span>
        <span class=price4>
            $100
        </span>
    </div>
    <div class=item2>
        <span class=text4>
            4 Text text text text text text text text text text text text text text text text text
        </span>
        <span class=price4>
            $100
        </span>
    </div>
</div>

<h4><code>mix-blend-mode: darken;</code> (4th method before 2nd)</h4>
<div class="width">
    <div class=item2>
        <span class=text4>
            4 Text text
        </span>
        <span class=price4>
            $100
        </span>
    </div>
    <div class=item2>
        <span class=text4>
            4 Text text text text text text text text text text text text text text text text text
        </span>
        <span class=price4>
            $100
        </span>
    </div>

    <br />

    <div class=item2>
        <span class=text2>
            <span class=bg2>
                2 Text text
            </span>
        </span>
        <span class=price2>
            <span class=bg2>
                $100
            </span>
        </span>
    </div>
    <div class=item2>
        <span class=text2>
            <span class=bg2>
                2 Text text text text text text text text text text text text text text text text text
            </span>
        </span>
        <span class=price2>
            <span class=bg2>
                $100
            </span>
        </span>
    </div>
</div>

Even the W3C official source has some disparity. You may see inconsistent rendering or poor support for invented features (such as non-monospace/preformatted, lead zero padding, varied currency widths).

Using a shim or Web Component such as Leader-Line would be advisable. I would recommend you experiment with dash-animation to increase visibility on busy backgrounds (at the expense of annoying someone with a busy leader).

You can modify technique 1 to move the dot functionality from the text to another span:

<div class=item1>
  <span class=text1>
    Text
  </span>
  <span class=line1></span>
  <span class=price1>
    $100
  </span>
</div>
<div class=item1>
  <span class=text1>
    Text text text text text text text text text text text text
  </span>
   <span class=line1></span>
  <span class=price1>
    $100
  </span>
</div>
<div class=item1>
  <span class=text1>
    Text text text text text text text text text text text text
  </span>
</div>
.item1 {
  display: flex;
}
.line1 {
  flex-grow: 1;
  position: relative;
  overflow: hidden;
}
.line1::after {
  content: '';
  position: absolute;
  bottom: 0;
  border-bottom: 1px dotted grey;
  width: 100%;
}
.price1 {
  align-self: flex-end;
}

This works as intended:

This is the output

And a JSFiddle

.item1 {
  display: flex;
  font-size:32px;
  color:#808080;
}
.line1 {
  flex-grow: 1;
  position: relative;
  overflow: hidden;
}
.line1::after {
  content: '';
  position: absolute;
  bottom: 0;
  border-bottom: 1px dotted #808080;
  width: 100%;
}
.price1 {
  align-self: flex-end;
}
<div class=item1>
  <span class=text1>Text text</span>
  <span class=line1></span>
  <span class=price1>$100</span>
</div>
<div class=item1>
  <span class=text1>Text text text text text text text text text</span>
   <span class=line1></span>
  <span class=price1>$100</span>
</div>
<div class=item1>
  <span class=text1>Text text text text text text text</span>
</div>

Related