Text Resizer

This text will resize to fit its container on a single line.

The text resizer component automatically adjusts text size to fit within its container width on a single line. This is useful for responsive designs where you want text to scale based on container size.

Basic Usage

Simply wrap your text content in the text-resizer component:

html
<ap-text-resizer> This text will resize to fit its container </ap-text-resizer>

By default it will on scale font sizes down. If you want to allow it scale up as well, just add the allow-scale-up boolean attribute.

html
<ap-text-resizer allow-scale-up> This text will resize to fit its container </ap-text-resizer>

Important Notes

  • This is a progressive enhancement. As such, make sure that the element still works and looks good without the JavaScript running to adjust text size.
  • The component works best with a single sentence.
  • You should treat this element as a fancy <span> element. If you want to get technical, treat it as phrasing content.
  • Depending on your stylesheet, you may need to adjust the font settings of the parent element and/or this element so that this element works correctly. This should be an edge case situation but worth pointing out.
    • For example, if you set a non-relative/non-scalar line height for the font, you may get alignment or overflow issues.

Example of a typical scenario

Example 1: This text will resize and scale down to fit its container on a single line.

html
<style> #text-downsizer-container { width: 500px; --font: 6rem/1 var(--font-family); font: var(--font); p { font: inherit; } } </style> <div id="text-downsizer-container"> <p> <ap-text-resizer> Example 1: This text will resize and scale down to fit its container on a single line. </ap-text-resizer> </p> </div>
Example 2: This text will resize and scale up to fit its container on a single line.
html
<style> #text-upsizer-container { width: 500px; font: 0.5rem/1 var(--font-family); } </style> <div id="text-upsizer-container"> <ap-text-resizer> Example 2: This text will resize and scale up to fit its container on a single line. </ap-text-resizer> </div>

Of note, it shouldn't matter if the text resizer element is a direct child of the limiting container or not—it should just scale. The first example puts it inside of a paragraph element and the second does not.

The Nitty-Gritty of How It Works

In most cases, <ap-text-resizer> should just work and you shouldn't need to know what it's doing under the hood. However, due to how CSS works, you may run into edge cases where you need to know how this works to work around it. If you'd just like to see the source code of the function that adjusts the font size, skip to the bottom of this section.

This element does not rely on the Shadow DOM. As such, it directly affects its parent element and itself. In terms of hydration and managing state with modern front-end rendering libraries, I haven't noticed an issue with this process, but you may wish to exclude it from being managed by the library, such as using React's dangerouslySetInnerHTML.

In terms of affecting the parent element, we set the overflow property in the style attribute to "scroll" temporarily, before changing it back to whatever it orginally was or getting rid of it altogether along with the style attribute if there are no other properties set.

In terms of affecting the text-resizer element itself, we add display: inline-block; width: max-content to the style attribute, again, temporarily, before resetting it at the end.

We do all of this in order to calculate the ratio between widths and scale the font size accordingly.

Where you might run into issues is if you set relevant properties (such as the ones mentioned here or related properties) on the elements themselves or their ancestors that override what we set. This would only happen if you directly set the style attribute and/or use the !important declaration.

The code from the actual function that adjusts the font-size.

javascript
// Prevent multiple calls to this function while resizing if (this.isResizing) { setTimeout(this.adjustFontSize.bind(this), 16); return; } // Save original parent overflow property if set via style attribute const parent = this.parentElement; const ogOverflow = parent.style.overflow; parent.style.setProperty("overflow", "scroll"); // Save original display and width properties of this element if set via style attribute const [ogDisplay, ogWidth] = [this.style.display, this.style.width]; this.style.setProperty("display", "inline-block"); this.style.setProperty("width", "max-content"); // Remove any existing font size styles to avoid conflicts this.style.removeProperty("font-size"); // Get the ratio of the parent element's width to its scroll/overflow width to figure out how much to scale down. let width = parent.clientWidth; let ratio = width / parent.scrollWidth; // If the parent element is not scrollable, use its client width to scale up if (ratio === 1 && this.hasAttribute("allow-scale-up")) { ratio = width / this.clientWidth; } // Extract the font size from the parent element's computed style let [empty, num, unit] = window .getComputedStyle(parent) .fontSize.split(/([\d\.]+)/); // Scale the font size by the ratio const size = `${(ratio * parseInt(num)).toFixed(3)} ${unit}`; this.style.setProperty("font-size", size); // Reset the styles to the original values if (ogOverflow) parent.style.setProperty("overflow", ogOverflow); else parent.style.removeProperty("overflow"); if (parent.style.length === 0) parent.removeAttribute("style"); if (ogDisplay) this.style.setProperty("display", ogDisplay); else this.style.removeProperty("display"); if (ogWidth) this.style.setProperty("width", ogWidth); else this.style.removeProperty("width"); if (this.style.length === 0) this.removeAttribute("style"); this.isResizing = false;