Force Non-Monospace Font into Fixed Width Using CSS

CssFontsTypography

Css Problem Overview


Is there any way to force a font to be monospaced using CSS?

By this I mean, using a non-monospace font, can you force the browser to render each character at a fixed width?

Css Solutions


Solution 1 - Css

If this is for aligning digits in tables where some fonts (with traditional typography) render them by default with variable width (e.g. Segoe UI on Windows), you should look into CSS properties for:

font-variant-numeric: tabular-nums;

(this disables the proportional-nums default value for the numeric-spacing variant supported at least by OpenType fonts, and possibly by other font formats supported by the text renderer of your web browser for your particular platform)

No JavaScript needed! It is the cleanest way to disable the variable-width glyphs in these fonts and force them to use tabular digits (this generally uses in the same glyphs in the same font, but their leading and trailing gap is increased so the 10 digits from 0 to 9 will render at the same width; however some font may avoid the visual variable interdigit spacing and will slightly widden some digits, or could add bottom serifs to the foot of digit 1.

Note that this does not disable the variable height observed with Segoe UI (such as some digits will be x-height only like lowercase letters, others will have ascenders or descenders). These traditional digit forms may be disabled with CSS too, using

font-variant-numeric: lining-nums;

(this disables the default oldstyle-nums value for the numeric-figure variant supported at least by OpenType fonts, and by possibly other font formats supported by the text renderer of your web browser for your particular platform)

You can combine both:

font-variant-numeric: tabular-nums lining-nums;

--

The snippet below demonstrates this using a single proportional font (not monospaced!) featuring shape variants for digits, such as 'Segoe UI' on Windows and shows the different horizontal and vertical alignments produced.

Note that this style does not prohibit digits to change width if different styles like bold or italic is applied instead of medium roman as shown below because these will use different fonts with their own distinct metrics (this is not warrantied as well with all monospace fonts).

html { font-family: 'Segoe UI'; /* proportional with digit variants */ }
table { margin: 0; padding: 0; border: 1px solid #AAA; border-collapse: collapse; }
th, td { vertical-align:top; text-align:right; }
.unset       { font-variant-numeric: unset; }
.traditional { font-variant-numeric: proportional-nums oldstyle-nums; }
.lining      { font-variant-numeric: proportional-nums lining-nums;   }
.tabular-old { font-variant-numeric: tabular-nums      oldstyle-nums; }
.tabular-new { font-variant-numeric: tabular-nums      lining-nums;   }
.normal      { font-variant-numeric: normal; }

<table>
<tr><th>unset<td><table width="100%" class="unset">
  <tr><td>Rs12,34,56,789.00/Gal<td><i>Difference Rs86,41,97,532.11/Gal
  <tr><td>Rs98,76,54,321.11/Gal<td><b>Total Rs1,11,11,11,110.11/Gal
  </table>
<tr><th>traditional<td><table width="100%" class="traditional">
  <tr><td>Rs12,34,56,789.00/Gal<td><i>Difference Rs86,41,97,532.11/Gal
  <tr><td>Rs98,76,54,321.11/Gal<td><b>Total Rs1,11,11,11,110.11/Gal
  </table>
<tr><th>lining<td><table width="100%" class="lining">
  <tr><td>Rs12,34,56,789.00/Gal<td><i>Difference Rs86,41,97,532.11/Gal
  <tr><td>Rs98,76,54,321.11/Gal<td><b>Total Rs1,11,11,11,110.11/Gal
  </table>
<tr><th>tabular-old<td><table width="100%" class="tabular-old">
  <tr><td>Rs12,34,56,789.00/Gal<td><i>Difference Rs86,41,97,532.11/Gal
  <tr><td>Rs98,76,54,321.11/Gal<td><b>Total Rs1,11,11,11,110.11/Gal
  </table>
<tr><th>tabular-new<td><table width="100%" class="tabular-new">
  <tr><td>Rs12,34,56,789.00/Gal<td><i>Difference Rs86,41,97,532.11/Gal
  <tr><td>Rs98,76,54,321.11/Gal<td><b>Total Rs1,11,11,11,110.11/Gal
  </table>
<tr><th>normal<td><table width="100%" class="normal">
  <tr><td>Rs12,34,56,789.00/Gal<td><i>Difference Rs86,41,97,532.11/Gal
  <tr><td>Rs98,76,54,321.11/Gal<td><b>Total Rs1,11,11,11,110.11/Gal
  </table>
</table>

Reference: https://developer.mozilla.org/docs/Web/CSS/font-variant-numeric

Solution 2 - Css

Why not think outside the box and inside a table for this:

<table cellpadding="0" cellspacing="0">
<tr><td>T</td><td>h</td><td>e</td><td></td><td>r</td><td>a</td><td>i</td><td>n</td><td></td><td>i</td><td>n</td><td></td><td>S</td><td>p</td><td>a</td><td>i</td><td>n</td><td></td><td>s</td><td>t</td><td>a</td><td>y</td><td>s</td></tr>
<tr><td>m</td><td>a</td><td>i</td><td>n</td><td>l</td><td>y</td><td></td><td>i</td><td>n</td><td></td><td>t</td><td>h</td><td>e</td><td></td><td>p</td><td>l</td><td>a</td><td>i</td><td>n</td><td>s</td><td>.</td></tr>
</table>

Solution 3 - Css

You can't do this with CSS. Even if you could, the result will look horrible:

enter image description here

If you really do need to do this, you could use JavaScript to wrap each individual character in an element (or just do it by hand):

function wrap_letters($element) {
    for (var i = 0; i < $element.childNodes.length; i++) {
        var $child = $element.childNodes[i];

        if ($child.nodeType === Node.TEXT_NODE) {
            var $wrapper = document.createDocumentFragment();

            for (var i = 0; i < $child.nodeValue.length; i++) {
                var $char = document.createElement('span');
                $char.className = 'char';
                $char.textContent = $child.nodeValue.charAt(i);

                $wrapper.appendChild($char);
            }

            $element.replaceChild($wrapper, $child);
        } else if ($child.nodeType === Node.ELEMENT_NODE) {
            wrap_letters($child);
        }
    }
}

wrap_letters(document.querySelectorAll('.boxes')[0]);
wrap_letters(document.querySelectorAll('.boxes')[1]);

.char {
    outline: 1px solid rgba(255, 0, 0, 0.5);
}

.monospace .char {
    display: inline-block;
    width: 15px;
    text-align: center;
}

<h2 class="boxes">This is a title</h2>
<h2 class="boxes monospace">This is a title</h2>

Solution 4 - Css

I've just found the text-transform: full-width; experimental keyword, which:

> [...] forces the writing of a character [...] inside a square [...] > > text-transform | MDN

Combined with negative letter-spacing, you can get not-so-horrible results:

Fixed-width sans-serif font to mock monospaced font

<style>
pre {
  font-family: sans-serif;
  text-transform: full-width;
  letter-spacing: -.2em;
}
</style>

<!-- Fixed-width sans-serif -->
<pre>
. i I 1  | This is gonna be awesome.
ASDFGHJK | This is gonna be awesome.
</pre>

<!-- Default font -->
. i I 1  | This is gonna be awesome.
<br>
ASDFGHJK | This is gonna be awesome.

Solution 5 - Css

Well, you didn't say using only CSS. It is possible to do this with just a little bit of Javascript to wrap each letter in a span. The rest is in CSS...

window.onload = function() {
  const secondP = document.getElementById('fixed');
  const text = secondP.innerText;
  const newText = text.split('').map(c => {
    const span = `<span>${c}</span>`;
    return span;
  }).join('');
  secondP.innerHTML = newText;
}

p {
  position: relative;
  border: 1px solid black;
  border-radius: 1em;
  padding: 1em;
  margin: 3em 1em;
}

p::after {
  content: attr(name);
  display: block;
  background-color: white;
  color: green;
  padding: 0 0.5em;
  position: absolute;
  top: -0.6em;
  left: 0.5em;
}

#fixed span {
  display: inline-block;
  width: 1em;
  text-align: center;
}

<p id="variable" name="Variable Width">It might not look nice, but with a little Javascript, I can force a variable width font to act like a fixed-width font.</p>
<p id="fixed" name="Fixed Width">It might not look nice, but with a little Javascript, I can force a variable width font to act like a fixed-width font.</p>

In a paragraph with regular text, it looks terrible, but There are instances when this makes sense. Icon fonts and Unicode symbols could both make use of this technique.

I found this question while trying to find a solution for Unicode symbols that were shifting regular text to the right when they were replaced with other Unicode symbols.

Solution 6 - Css

I've done a verry pretty thing sometimes for countdowns:

HTML:

<div class="counter">
    <span class="counter-digit counter-digit-0">2</span>
    <span class="counter-digit counter-digit-1">4</span>
    <span class="counter-digit counter-digit-divider">/</span>
    <span class="counter-digit counter-digit-2">5</span>
    <span class="counter-digit counter-digit-3">7</span>
</div>

SCSS:

$digit-width: 18px;
.counter {

    text-align: center;
    font-size: $digit-width;

    position: relative;

    width : $digit-width * 4.5;
    margin: 0 auto;
    height: 48px;
}
.counter-digit {

    position: absolute;
    top: 0;
    width: $digit-width;
    height: 48px;
    line-height: 48px;
    padding: 0 1px;

    &:nth-child(1) { left: 0; text-align: right; }
    &:nth-child(2) { left: $digit-width * 1; text-align: left;}
    &:nth-child(3) { left: $digit-width * 2; text-align: center; width: $digit-width / 2} // the divider (/)
    &:nth-child(4) { left: $digit-width * 2.5; text-align: right;}
    &:nth-child(5) { left: $digit-width * 3.5; text-align: left;}
}

Solution 7 - Css

You can wrap the seconds digits in a span and style it like... .time-seconds {display: inline-block;width: .52em;text-align: center;} See Snippet.

function padlength(what) {
  var output = (what.toString().length == 1) ? "0" + what : what;
  return output;
}

function displaytime() {
  var serverdate = new Date();
  var dd = "am";
  var hh = serverdate.getHours();
  var h = hh;
  if (h >= 12) {
    h = hh - 12;
    dd = "pm";
  }
  if (h == 0) {
    h = 12;
  }
  h = parseInt(h);
  var sec = String(padlength(serverdate.getSeconds()));
  var timeFixed = h + ':' + padlength(serverdate.getMinutes()) + ':<span class="time-seconds">' + sec.charAt(0) + '</span><span class="time-seconds">' + sec.charAt(1) + '</span> ' + dd;
  timeVariable = h + ':' + padlength(serverdate.getMinutes()) + ':' + sec + ' ' + dd;
  document.getElementById("servertime-fixed").innerHTML = timeFixed;
  document.getElementById("servertime-variable").innerHTML = timeVariable;
}

window.onload = function() {
  displaytime();
  setInterval("displaytime()", 1000);
};

center {
  font-size: 3em;
  font-family: Cursive;
}

.time-seconds {
  display: inline-block;
  width: .52em;
  text-align: center;
}

<html>

<body>
  <center id="servertime-fixed">H:MM:SS mm</center>
  <center id="servertime-variable">H:MM:<span class="time-seconds">S</span><span class="time-seconds">S</span> mm</center>
</body>

</html>

Solution 8 - Css

No, not unless it's an actual mono-spaced font.

Solution 9 - Css

No, there is no way to force anything in CSS. And there isn’t even a way to suggest that a non-monospace font be rendered as a monospace font.

Solution 10 - Css

i just had the same problem. my font didn't support font-variant-numeric: tabular-nums (which i knew about) and the other solutions didn't suit me, so i came up with this one - in my case i just had to expand letter spacing and then squash the (gigantic) zeros to make it look acceptable:

CSS:

.squashzeros { letter-spacing:.2em; }
.squashzeros span { display:inline-block; margin:0 -.09em; }

JS:

document.querySelectorAll('.squashzeros').forEach((o)=>{
		o.innerHTML = o.innerText.replaceAll(/0/g,'<span>0</span>');
	});

unfortunately i found no css-only solution.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJon RaaschView Question on Stackoverflow
Solution 1 - Cssverdy_pView Answer on Stackoverflow
Solution 2 - CssDour High ArchView Answer on Stackoverflow
Solution 3 - CssBlenderView Answer on Stackoverflow
Solution 4 - CssMárton TamásView Answer on Stackoverflow
Solution 5 - CssVinceView Answer on Stackoverflow
Solution 6 - CssMettin ParzinskiView Answer on Stackoverflow
Solution 7 - CssTerry RiegelView Answer on Stackoverflow
Solution 8 - CssjeremyharrisView Answer on Stackoverflow
Solution 9 - CssJukka K. KorpelaView Answer on Stackoverflow
Solution 10 - CssnïköView Answer on Stackoverflow