381

I know that this could be solved fairly easily with Javascript, but I'm only interested in a pure CSS solution.

I want a way to dynamically resize text so that it always fits into a fixed div. Here is the sample markup:

<div style="width: 200px; height: 1em; overflow: hidden;">
  <p>Some sample dynamic amount of text here</p>
</div>

I was thinking that maybe this could be possible by specifying the width of the container in ems, and getting the font-size to inherit that value?

captainsac
  • 2,484
  • 3
  • 27
  • 48
DMTintner
  • 14,405
  • 5
  • 35
  • 32
  • 1
    Wow, wait. The specifying of the `width` in `em`s thing goes the other way around. It's the `width` that depends on the `font-size`. @JosephSilber That's exactly what I thought. – Ana Jan 21 '13 at 02:24
  • 1
    I'm curious about this question. What is the drive to use a pure css solution instead of writing a simple javascript function? – Austin Mullins Jan 21 '13 at 02:56
  • 39
    The drive is simply because the problem exists and a pure CSS solution would be amazing. Think about the possibilities of applying just a few styles and knowing that your dynamic content will never break the design. – DMTintner Jan 21 '13 at 10:24
  • 4
    Just in case anyone stumbles across this question and doesnt mind using JS, here's a plugin for doing it http://fittextjs.com/ – DMTintner Jan 21 '13 at 10:46
  • 2
    fitText has limits. For example I only want to run it for small screens, beyond 500px width or so, I don't want my headings to blow up any more. This requires writing more JavaScript. The separation of concerns breaks down very quickly when you use JavaScript for layout. It's never just a quick one liner. – Costa Michailidis Jul 10 '15 at 19:28
  • OMG, so many complicated answers. [just use css rem](https://snook.ca/archives/html_and_css/font-size-with-rem). – ToolmakerSteve May 08 '19 at 21:54

14 Answers14

594

Note: This solution changes based on viewport size and not the amount of content

I just found out that this is possible using VW units. They're the units associated with setting the viewport width. There are some drawbacks, such as lack of legacy browser support, but this is definitely something to seriously consider using. Plus you can still provide fallbacks for older browsers like so:

p {
    font-size: 30px;
    font-size: 3.5vw;
}

http://css-tricks.com/viewport-sized-typography/ and https://medium.com/design-ux/66bddb327bb1

aWebDeveloper
  • 36,687
  • 39
  • 170
  • 242
DMTintner
  • 14,405
  • 5
  • 35
  • 32
  • 7
    Oh, I had missed those! So now we have em, ex, ch, rem, vh, vh, vmin, vmax, px, mm, cm, in, pt and pc! [mozilla doc](https://developer.mozilla.org/en-US/docs/Web/CSS/length) – some Aug 20 '14 at 17:55
  • 2
    vw,vh,vmax,vmin units sometimes don't work in some android and in ie8. – spicykimchi Oct 14 '14 at 02:21
  • 47
    Pro tip: you can prevent the text from getting too small and get some control back by doing `font-size:calc(100% + 2vw);` or similar. It's kinda `min-font-size`. Browser support for `calc` is similar to `vw`. – Prinzhorn Oct 29 '14 at 19:50
  • 149
    Upvoted, but now I'm wondering if this actually answers the original question. My understanding is that your text length is dynamic and you want to change the font size to always fit the width of your `div` (200px). How does this solve that problem? – Floremin Feb 03 '15 at 15:40
  • 30
    I agree with @Floremin's sentiment. This scales ALL font sizes based on view-port width/height, not the container of the text's width/height. – MickJuice Mar 18 '15 at 14:54
  • 31
    @Floremin is right, this doesn't address the question. This calculates font-size as a function of container width, not as a function of string length as it relates to container width – henry Jul 07 '15 at 16:32
  • 1
    f.y.i. Chrome does not print elements defined with viewport units. https://code.google.com/p/chromium/issues/detail?id=382313 – Donny V. Dec 20 '15 at 04:57
  • Fantastic solution. Any way to set `max-font-size` using this method? – Alex Mar 07 '16 at 08:58
  • 3
    Another vote to @Floremin's. This does not solve the problem in the question. – Mohy Eldeen Mar 09 '16 at 21:28
  • 8
    The answer addresses a different issue. using viewport units will make the font size react to the size of the viewport, not the amount of characters of the text. – Pasha Skender Jun 21 '16 at 21:41
  • vw is not applicable in IE8 :( , How can I set font size as responsive in IE8? – Hosein Aqajani Jan 31 '17 at 07:54
  • 1
    @H.Aqjn Who are you serving that is still using IE8? – Alexander Jun 29 '17 at 15:28
  • 1
    The real answer is that it is not possible with only CSS – Alexander Jun 29 '17 at 17:05
  • Somehow `vw` or `vh` do not work for me. I have to adjust the numbers a lot (kind of defeating the purpose of responsive code). – Anupam Dec 26 '17 at 16:01
  • Can someone please verbalise this, for my understanding: `font-size:calc(100% + 2vw);`. 100% of the viewport plus 2%? So characters become 102%vw? That doesn't sound right... I mean, not that it's wrong, just how I'm reading it... – ptrcao Jan 26 '18 at 23:59
126

Edit: Watch out for attr() Its related to calc() in css. You may be able to achieve it in future.

Unfortunately for now there isn't a css only solution. This is what I suggest you do. To your element give a title attribute. And use text-overflow ellipsis to prevent breakage of the design and let user know more text is there.

<div style="width: 200px; height: 1em; text-overflow: ellipsis;" title="Some sample dynamic amount of text here">
 Some sample dynamic amount of text here
</div>

.

.

.

Alternatively, If you just want to reduce size based on the viewport. CSS3 supports new dimensions that are relative to view port.

body {
   font-size: 3.2vw;
}
  1. 3.2vw = 3.2% of width of viewport
  2. 3.2vh = 3.2% of height of viewport
  3. 3.2vmin = Smaller of 3.2vw or 3.2vh
  4. 3.2vmax = Bigger of 3.2vw or 3.2vh see css-tricks.com/.... and also look at caniuse.com/....
aWebDeveloper
  • 36,687
  • 39
  • 170
  • 242
57

You might be interested in the calc approach:

font-size: calc(4vw + 4vh + 2vmin);

done. Tweak values till matches your taste.

Source: https://codepen.io/CrocoDillon/pen/fBJxu

KhoPhi
  • 9,660
  • 17
  • 77
  • 128
18

The only way would probably be to set different widths for different screen sizes, but this approach is pretty inacurate and you should use a js solution.

h1 {
    font-size: 20px;
}

@media all and (max-device-width: 720px){
    h1 {
        font-size: 18px;
    }
}

@media all and (max-device-width: 640px){
    h1 {
        font-size: 16px;
    }
}

@media all and (max-device-width: 320px){
    h1 {
        font-size: 12px;
    }
}
Sven Rojek
  • 5,476
  • 2
  • 35
  • 57
9

For reference, a non-CSS solution:

Below is some JS that re-sizes a font depending on the text length within a container.

Codepen with slightly modified code, but same idea as below:

function scaleFontSize(element) {
    var container = document.getElementById(element);

    // Reset font-size to 100% to begin
    container.style.fontSize = "100%";

    // Check if the text is wider than its container,
    // if so then reduce font-size
    if (container.scrollWidth > container.clientWidth) {
        container.style.fontSize = "70%";
    }
}

For me, I call this function when a user makes a selection in a drop-down, and then a div in my menu gets populated (this is where I have dynamic text occurring).

    scaleFontSize("my_container_div");

In addition, I also use CSS ellipses ("...") to truncate yet even longer text too, like so:

#my_container_div {
    width: 200px; /* width required for text-overflow to work */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

So, ultimately:

  • Short text: e.g. "APPLES"

    Fully rendered, nice big letters.

  • Long text: e.g. "APPLES & ORANGES"

    Gets scaled down 70%, via the above JS scaling function.

  • Super long text: e.g. "APPLES & ORANGES & BANAN..."

    Gets scaled down 70% AND gets truncated with a "..." ellipses, via the above JS scaling function together with the CSS rule.

You could also explore playing with CSS letter-spacing to make text narrower while keeping the same font size.

Kalnode
  • 9,386
  • 3
  • 34
  • 62
8
calc(42px + (60 - 42) * (100vw - 768px) / (1440 - 768));

use this equation.

For anything larger or smaller than 1440 and 768, you can either give it a static value, or apply the same approach.

The drawback with vw solution is that you cannot set a scale ratio, say a 5vw at screen resolution 1440 may ended up being 60px font-size, your idea font size, but when you shrink the window width down to 768, it may ended up being 12px, not the minimal you want. With this approach, you can set your upper boundary and lower boundary, and the font will scale itself in between.

Shuwei
  • 774
  • 6
  • 7
3

As many mentioned in comments to @DMTinter's post, the OP was asking about the number ("amount") of characters changing. He was also asking about CSS, but as @Alexander indicated, "it is not possible with only CSS". As far as I can tell, that seems to be true at this time, so it also seems logical that people would want to know the next best thing.

I'm not particularly proud of this, but it does work. Seems like an excessive amount of code to accomplish it. This is the core:

function fitText(el){
  var text = el.text();
  var fsize = parseInt(el.css('font-size'));
  var measured = measureText(text, fsize);

  if (measured.width > el.width()){
    console.log('reducing');
    while(true){
      fsize = parseInt(el.css('font-size'));
      var m = measureText(text, fsize );
      if(m.width > el.width()){
        el.css('font-size', --fsize + 'px');
      }
      else{
        break;
      }
    }
  }
  else if (measured.width < el.width()){
    console.log('increasing');
    while(true){
      fsize = parseInt(el.css('font-size'));
      var m = measureText(text, fsize);
      if(m.width < el.width()-4){ // not sure why -4 is needed (often)
        el.css('font-size', ++fsize + 'px');
      }
      else{
        break;
      }
    }
  }
}

Here's a JS Bin: http://jsbin.com/pidavon/edit?html,css,js,console,output
Please suggest possible improvements to it (I'm not really interested in using canvas to measure the text...seems like too much overhead(?)).

Thanks to @Pete for measureText function: https://stackoverflow.com/a/4032497/442665

jbobbins
  • 1,221
  • 3
  • 15
  • 28
  • This is great, I am using a modified version of this for a React project that needed to clamp text with varying text lengths. Though I wonder how much work it would require to allow multiple lines. – Harrison Howard Apr 27 '23 at 00:30
  • @HarrisonHoward thanks, it seems allowing multiple lines would add complexity, but I haven't given it much thought. If you do/did anything with that, please add a link to your work here because that might be very useful. Thank you. – jbobbins May 18 '23 at 15:24
  • I made small attempts at achieving this but all were unsuccessful due to my attempts determining the height after it had already been measured. I would expect measureText to be able to pull this off in some capacity? Not sure, I'm just using line-clamp with a minimum font-size now. – Harrison Howard May 23 '23 at 04:26
  • I've made some improvements to the measureText and scaleFontSize that should hopefully reduce some overhead and challenges you may face in other frameworks/libraries. scaleFontSize now uses math to calculate the next font size instead of looping and measureText will use the current element to measure rather than spawning a new one. https://jsfiddle.net/harrisonhowardax/jLrsn40u/13/ – Harrison Howard Jun 20 '23 at 06:36
1

I got this dynamic font size calc() from BootStrap somewhere and tweaked it to suit. Basing off 4pt system and rem https://www.finsweet.com/client-first/docs/sizes for a Webflow project:

html {font-size: 16px;}

@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');
body {font-family: 'Poppins', sans-serif;}

/*---SETUP BASE SIZE ---*/
html {font-size: 16px;}

/*---LINE-HEIGHTS + MARGINS---*/
[class^="display"], h1, h2, h3, h4 {
    margin-top: 0;
    margin-bottom: 1rem;
    font-weight: 600;
}

.display-1, .display-2, .display-3, .display-4 {
    line-height: 1.2;
    
}

h1, h2, h3, h4 {
    line-height: 1.4;
}

p, ul, ol {
    margin-bottom: 0.7rem;
    line-height: 1.45;
}

.lead {
    margin-bottom: 1rem;
    line-height: 1.4;
}

/*---FONT SIZES 1279px DOWN---*/
@media (max-width: 1279px) {
    .display-1 {
        font-size: calc(1.625rem + 4.5vw);
    }

    .display-2 {
        font-size: calc(1.575rem + 3.9vw);
    }

    .display-3 {
        font-size: calc(1.525rem + 3.3vw);
    }

    .display-4 {
        font-size: calc(1.475rem + 2.7vw);
    }
  
    /*---HEADINGS---*/
    h1 {
        font-size: calc(1.375rem + 1.5vw);
    }

    h2 {
        font-size: calc(1.325rem + 0.9vw);
    }

    h3 {
        font-size: calc(1.3rem + 0.6vw);
    }

    h4 {
        font-size: calc(1.275rem + 0.3vw);
    }

    /*---PARAGRAPHS/UL/OL---*/
    p, ul, ol  {
        font-size: calc(0.823rem + 0.3vw);
    }

    .lead {
        font-size: calc(1.01rem + 0.3vw);
    }
}

/*---FONT SIZES ABOVE 1279px---*/
@media screen and (min-width: 1280px) {
    .display-1 {
        font-size: 5.22rem;
    }

    .display-2 {
        font-size: 4.7rem;
    }

    .display-3 {
        font-size: 4.16rem;
    }

    .display-4 {
        font-size: 3.63rem;
    }
    /*---HEADINGS---*/
    h1 {
        font-size: 2.58rem;
    }

    h2 {
        font-size: 2.05rem;
    }

    h3 {
        font-size: 1.78rem;
    }

    h4 {
        font-size: 1.52rem;
    }

    p, ul, ol {
        font-size: 1.0625rem;
    }

    .lead {
        font-size: 1.25rem;
    }
}
<section>
    <div class="container">
        <p style="color:#8C8C8C;"><i>Note: Resize window too see text grow/shrink in browser window <= 1279px</i></p>
        <br>
        <h1 class="display-1">Display 1</h1>
        <h1 class="display-2">Display 2</h1>
        <h1 class="display-3">Display 3</h1>
        <h1 class="display-4">Display 4</h1>
        <br>
        <br>
        <br>
        <br>
        <h1>h1. The quick brown fox jumps over the lazy dog</h1>
        <h2>h2. The quick brown fox jumps over the lazy dog</h2>
        <h3>h3. The quick brown fox jumps over the lazy dog</h3>
        <h4>h4. The quick brown fox jumps over the lazy dog</h4>
        <p>The earliest known appearance of the phrase was in The Boston Journal. In an article titled "Current Notes" in the February 9, 1885, edition, the phrase is mentioned as a good practice sentence for writing students: "A favorite copy set by writing teachers for their pupils is the following, because it contains every letter of the alphabet: 'A quick brown fox jumps over the lazy dog.'"[2] Dozens of other newspapers published the phrase over the next few months, all using the version of the sentence starting with "A" rather than "The"</p>
        <p>The earliest known use of the phrase starting with "The" is from the 1888 book Illustrative Shorthand by Linda Bronson.[4] The modern form (starting with "The") became more common even though it is slightly longer than the original (starting with "A").</p>
        <p>A 1908 edition of the Los Angeles Herald Sunday Magazine records that when the New York Herald was equipping an office with typewriters "a few years ago", staff found that the common practice sentence of "now is the time for all good men to come to the aid of the party" did not familiarize typists with the entire alphabet, and ran onto two lines in a newspaper column. They write that a staff member named Arthur F. Curtis invented the "quick brown fox" pangram to address this.</p>
        <br>
        <br>
        <br>
        <br>
        <p class="lead">Lead paragraph: As the use of typewriters grew in the late 19th century.</p>
        <p>The phrase began appearing in typing lesson books as a practice sentence. Early examples include How to Become Expert in Typewriting: A Complete Instructor Designed Especially for the Remington Typewriter (1890),[6] and Typewriting Instructor and Stenographer's Hand-book (1892). By the turn of the 20th century, the phrase had become widely known. In the January 10, 1903, issue of Pitman's Phonetic Journal, it is referred to as "the well known memorized typing line embracing all the letters of the alphabet".</p>
        <p>Robert Baden-Powell's book Scouting for Boys (1908) uses the phrase as a practice sentence for signaling.</p>
        <p>The first message sent on the Moscow–Washington hotline on August 30, 1963, was the test phrase "THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG'S BACK 1234567890".</p>
        <br>
        <br>
        <br>
        <br>
        <ul class="list-unordered">
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
        </ul>
        <br>
        <br>
        <br>
        <br>
        <ol class="list-ordered">
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
        </ol>
        <br>
        <br>
        <br>
        <br>
    </div>
</section>

Enjoy

Kerry7777
  • 4,246
  • 1
  • 18
  • 28
0

This solution might also help :

$(document).ready(function () {
    $(window).resize(function() {
        if ($(window).width() < 600) {
            $('body').css('font-size', '2.8vw' );
        } else if ($(window).width() >= 600 && $(window).width() < 750) {
            $('body').css('font-size', '2.4vw');
        } 
         // and so on... (according to our needs)
        } else if ($(window).width() >= 1200) {
            $('body').css('font-size', '1.2vw');
        }
    }); 
  });

It worked for me well !

Gaurav
  • 437
  • 1
  • 6
  • 15
0

ok, your dynamic text must've come from some place. In my case this looks like:

<div class="large" :data-contentlength="Math.floor(item.name.length/7)">[[ item.name ]]</div>

and my css classes:

.large[data-contentlength="1"]{ font-size: 1.2em; }
.large[data-contentlength="2"]{ font-size: 1.1em; }
.large[data-contentlength="3"]{ font-size: 1.0em; }
.large[data-contentlength="4"]{ font-size: 0.9em; }
.large[data-contentlength="5"]{ font-size: 0.8em; }
.large[data-contentlength="6"]{ font-size: 0.7em; }
.large[data-contentlength="7"]{ font-size: 0.6em; }

I also have classes for "non-large" text:

[data-length="1"]{ font-size: 1.00em; }
...

edit: this becomes a bit easier when attr() is available in all browsers: https://developer.mozilla.org/en-US/docs/Web/CSS/attr#browser_compatibility

also, this is could be more dynamic if css could divide 2 unit-values (e.g. px and ch), for now this must be done manually.

see here:

https://jsfiddle.net/qns0pov2/3/

create a 1ch cube and see how large it is in your target unit (in the fiddle its px), calculate the amount of characters per line and use that value to get the perfect font-size for each content length.

the fiddle also shows the problems with that approach: the average character width is less than 1ch (which is based on 0) but there are characters like M which are larger (somewhere around 70%).

so if you wish to guarantee that the characters fit in the space adjust the fiddle such that: --ch-width: calc(8.109 * 1.7);

if you're more interested in the average case: --ch-width: calc(8.109 * 0.92);

Soraphis
  • 706
  • 7
  • 26
  • this is an under-rated solution! imo this answers the question, although it isn't 100% dynamic, it's possible to create several nice step sizes and font size variations. – DavidG Mar 11 '22 at 07:49
  • The post clearly states pure CSS, ```Math.floor(item.name.length)``` is not. – mloureiro Apr 13 '22 at 09:54
  • this is an example, you're free to write the contentlength in there by hand, as I've done in the linked fiddle – Soraphis Apr 14 '22 at 13:53
-1

If doing from scratch in Bootstrap 4

  1. Go to https://bootstrap.build/app
  2. Click Search Mode
  3. Search for $enable-responsive-font-sizes and turn it on.
  4. Click Export Theme to save your custom bootstrap CSS file.
joym8
  • 4,014
  • 3
  • 50
  • 93
-2

Why not set the class on the server side based on the number of characters?

    .NotMuchText {
        font-size: 20px;
    }

    .LotsOfText {
        font-size: 10px;
    }

I also wanted a non-javascript solution, CSS solution, and resorted to a PHP/CSS solution instead.

MickIsMe
  • 21
  • 1
-3

Create a lookup table that computes font-size based on the length of the string inside your <div>.

const fontSizeLookupTable = () => {
  // lookup table looks like: [ '72px', ..., '32px', ..., '16px', ..., ]
  let a = [];
  // adjust this based on how many characters you expect in your <div>
  a.length = 32;
  // adjust the following ranges empirically
  a.fill( '72px' ,     );
  a.fill( '32px' , 4 , );
  a.fill( '16px' , 8 , );
  // add more ranges as necessary
  return a;
}

const computeFontSize = stringLength => {
  const table = fontSizeLookupTable();
  return stringLength < table.length ? table[stringLength] : '16px';
}

Adjust and tune all parameters by empirical test.

Let Me Tink About It
  • 15,156
  • 21
  • 98
  • 207
-5

I used smth like this:

1.style.fontSize = 15.6/(document.getElementById("2").innerHTML.length)+ 'vw'

Where: 1 - parent div Id and 2 - Id of div with my text

IGreen
  • 1
  • 4
    This is not CSS. The question clearly states, it's easy with Javascript, but only looking for a pure CSS solution. – Steve Maris Dec 09 '20 at 21:05
  • I know, and I wasted so much time to solve it in CSS, but`ve foud it the easiest way to do so. I`m newbe in Stackoverflow, so didn`t mention that it`s for pure CSS theme. I`ll try to be more more presize next time. Thnx for youre feedback – IGreen Dec 10 '20 at 12:03