20

Suppose a text shows up as follows on an HTML page (just one word too long to fit on one line):

Lorem ipsum dolores amet foo
bar

How can one avoid with CSS that the last word appears on the last line, and force two (or more)?

Lorem ipsum dolores amet
foo bar
bart
  • 14,958
  • 21
  • 75
  • 105
  • 1
    Possible duplicate of [How can I prevent having just one hanging word on a new line in an HTML element?](https://stackoverflow.com/questions/31974448/how-can-i-prevent-having-just-one-hanging-word-on-a-new-line-in-an-html-element) – clickbait Aug 10 '18 at 00:40

7 Answers7

27

I don't think you can do this in pure CSS.

You would have to either put a non-breaking space in between the last two words:

foo bar

or put the last two words into a span:

<span style="white-space: nowrap">foo bar</span>
Pekka
  • 442,112
  • 142
  • 972
  • 1,088
9

I don't think it can be done in CSS alone but this is what I came up with to solve the one word per line problem. It will replace the last n spaces with a non breaking space for all the matched elements.

https://jsfiddle.net/jackvial/19e3pm6e/2/

    function noMoreLonelyWords(selector, numWords){

      // Get array of all the selected elements
      var elems = document.querySelectorAll(selector);
      var i;
      for(i = 0; i < elems.length; ++i){

        // Split the text content of each element into an array
        var textArray = elems[i].innerText.split(" ");

        // Remove the last n words and join them with a none breaking space
        var lastWords = textArray.splice(-numWords, numWords).join("&nbsp;");

        // Join it all back together and replace the existing
        // text with the new text
        var textMinusLastWords = textArray.join(" ");
        elems[i].innerHTML = textMinusLastWords + " " +  lastWords;
      }
    }

    // Goodbye lonely words
    noMoreLonelyWords("p", 3);
Jack Vial
  • 2,354
  • 1
  • 28
  • 30
6

Just wrote this dependency-free JS snippet that will solve this very problem

https://github.com/ajkochanowicz/BuddySystem

Essentially this is the source code

var buddySystem=function(e){var n=[],r=[]
n=e.length?e:n.concat(e),Array.prototype.map.call(n,function(e){var n=String(e.innerHTML)
n=n.replace(/\s+/g," ").replace(/^\s|\s$/g,""),r.push(n?e.innerHTML=n.replace(new RegExp("((?:[^ ]* ){"+((n.match(/\s/g)||0).length-1)+"}[^ ]*) "),"$1&nbsp;"):void 0)})}

and you can implement it by doing this

objs = document.getElementsByClassName('corrected');
buddySystem(objs);

Now you'll never have a word by itself for any tags with the corrected class.

You can also use jQuery if you want.

$(".corrected").buddySystem()

Check out the link for all possibilities.

Adam Grant
  • 12,477
  • 10
  • 58
  • 65
2

Can't be done with CSS, but Shaun Inman wrote a very useful bit of Javascript to help with this a while ago:

http://www.shauninman.com/archive/2006/08/22/widont_wordpress_plugin

It's a Wordpress plugin, but there are plenty of non-Wordpress clones around.

Phil Powell
  • 424
  • 2
  • 5
  • This post by Shaun is from 2006, a little old in my opinion to rely on. – bart Jan 28 '11 at 20:43
  • 4
    Age doesn't matter — it's a very simple Javascript pattern which worked when it was devised, and continues to work to this day. – Phil Powell Jan 28 '11 at 23:40
  • 2
    Don't understand why this answer has been voted down. Despite being a few year's old, Shaun's code is still very relevant. Seems a little ridiculous to disregard the validity of a something just because of it's age. – Phil Powell Feb 01 '11 at 11:20
1

If JavaScript is an option, one can use typogr.js, a JavaScript "typogrify" implementation. This particular filter is called Widont.

<script src="https://cdnjs.cloudflare.com/ajax/libs/typogr/0.6.7/typogr.min.js"></script>
<script>
document.body.innerHTML = typogr.widont(document.body.innerHTML);
</script>
</body>
K3---rnc
  • 6,717
  • 3
  • 31
  • 46
  • I wonder if it has any relation to the WordPress plugin of the same name. (Both are poorly named - these things *aren't* widows at all.) – BoltClock Dec 18 '17 at 05:15
  • Looks like typogrify cites Shaun Inman's implementation as the original, so... I guess I know who to point the finger at. – BoltClock Dec 18 '17 at 06:20
0

here's some newbie php that you might use if you wanted to do this in wordpress

function add_nobr_spans($string) {
  $words = explode(' ', $string);
  if (count($words) < 3){
    return $string;
  }
  $last_word = array_pop($words);
  while ($last_word == "") {
    $last_word = array_pop($words);
  }
  if (preg_match('#[<>]+#is', $last_word)) {
    return $string;
  }
  $almost_last_word = array_pop($words);
  if (preg_match('#[<>]+#is', $almost_last_word)) {
    return $string;
  }
  array_push($words, "<span class='nobr'>".$almost_last_word." ".$last_word."</span>");
  return implode(' ', $words);
}
function no_widows_for_end_tag($input, $end_tag){
  $split_stuff = explode($end_tag, $input);
  $split_stuff = array_map('add_nobr_spans', $split_stuff);
  $result = implode($end_tag, $split_stuff);
  return $result;
}
function no_widows($string) {
  $string = no_widows_for_end_tag($string, "</p>");
  $string = no_widows_for_end_tag($string, "</h3>");
  $string = no_widows_for_end_tag($string, "</h4>");
  $string = no_widows_for_end_tag($string, "</h5>");
  return $string;
}
jacob
  • 325
  • 4
  • 8
0

This question has been answered at https://stackoverflow.com/a/76701845/2076595. Nowadays, after many years of waiting, you can do this with CSS: text-wrap: pretty.

I initially added that answer to this question here as well, but a moderator deleted my answer saying I should flag questions as duplicates instead. All good, but unfortunately I don’t have enough points to cast duplicate-votes so here we are: me referring to the other answer. Sorry about that, but apparently that’s the rules.

For completeness: this questions is the OG question. These two other questions are duplicates and should get marked as duplicates of this question:

Bramus
  • 1,732
  • 14
  • 19