Force Non-Monospace Font into Fixed Width Using CSS
Asked Answered
S

11

105

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?

Smalltime answered 13/4, 2012 at 22:56 Comment(5)
The browser doesn't control this, it's all about the font. So no, it's not possible. What's wrong with just using a monospaced font?Fonteyn
It needs to match the rest of the typography.Smalltime
i also need to know opposite, how can i Force a Monospace Font To Display As Variable-Width Using CSS.Cajolery
☰ and 🔁 doesn't have the same width, but they could.Illuminating
@CodyGray Occasionally, browser DOES control it, and sadly it comes with the new MSIE6 - Chrome The Only One.... In Russian typography there is a symbol for numbers, №. It should be followed by the number (of a law, a chapter, a room, etc) with a half-sized space, and for HTML suggestion is to use  . Since it is somewhat exotic and lacking in MS Word, often there is no space at all. But when you make a align=justify text the gap width inside terms like "№1234" becomes different in different lines! Looks awful. And gotcha: Google changes the width of the № glyph to uglify the textArista
F
95

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

Note that for the 'Segoe UI' font, the 'normal' style is the same as 'tabular-new' (i.e. 'tabular-nums lining-nums'). This is not necessarily the case with all fonts that defined variants for digits, and not necessarily the case for all numeral systems (if not displaying Latin digits, e.g. with some native Brahmic digits, notably if they come from fallback fonts that don't support all these styles). As well this has no effect on non-digit characters, that preserve their variable width, but may define specific variants controled by other CSS properties or by using encoded variant controls.

If you use numbers noted in the Roman numeral system, these digits may still be rendered with a variable width (e.g. "MCMLXVIII" for 1968, unless the HTML contains some CSS styles allowing to change their semantic as letters into semantic as digits, or these digits are encoded differently, e.g. in CJK fonts with "fullwidth" variants, or in mathematical fonts defining specific variants of styles with specific code points instead of using styling and markup).

Fishbowl answered 18/11, 2017 at 4:1 Comment(8)
I wasn't aware of this CSS property, thanks. Sadly, Edge and IE don't support it.Govea
I don't know why IE or Edge does not supprot it, given that these font properties are defined in Segoe UI made for Windows. May be it's supported now in IE/Edge on Windows 10. But it works already with Chrome on Windows 10. And it works as well in MS Office, and Libre Office (with some advanced style properties, or via scripts) and with the Windows or .Net API themselves. May be you still need a "-ms-" prefix in IE/Edge (I did not test it).Fishbowl
Apparently the default option for styles of digits is browser dependant : on Chrome, fonts that define traditional forms for digits have these features enabled by default, and you need to disable it in numeric tables using these CSS styles. IE and Edge seem to disable these font features by default don't let us enable them by CSS.Fishbowl
Note that even if the default behavior (i.e. what the browser will render for the "unset" value of this property described above) is browser or font-dependant, using the other values of "font-variant-numeric:" will override this default, and will no longer be font-dependant. And this woks in Chrome/Cromium, Safari, and now also Edge; don't speak about IE, it's no longer supported since many years (as well as all windows versions for which it was built) and represents now less than 0.5% of worldmarket.Fishbowl
This can also be set via font-feature-settings: 'tnum' 1; or font-feature-settings: 'tnum' on;, see developer.mozilla.org/en-US/docs/Web/CSS/font-feature-settings , though note that MDN says: "Whenever possible, Web authors should instead use [...] font-variant-numeric"Stanwood
"font-feature-settings:" is only viable if you absolutely know that the text will be rendered with an Opentype font. Note that CSS does not only focus OpenType, there are also other font technologies (including notably SVG fonts, as well as PostScript fonts: think about printing, notably with CUPS on Linux, which supports may printers with a PostScript-based renderer, often Ghostscript, or rendering to a PDF, even if now PDF supports OpenType too). the "font-feature-settings:" are just low-level features to support various OpenType features, including many custom, non-standard or experimental.Fishbowl
As well the "font-variant-numeric:" advanced property could be used for non-Latin digits or Latin-scripts in cursive styles (that may need to activate or change other OpenType features, such as custom ligatures,) or other notations like diacritics (for multiples notably in Cyrillic) to force a positional decimal system or disable some language-sensitive replacements for "national/traditional digits" (e.g. in Chinese, Japanese, Arabic/Persian, Greek...). There's no universal solution except if we think about usage in tabular numeric data (accounting, statistics, game/sport scores, rankings...)Fishbowl
In other words, don't use "font-feature-settings:" in CSS for long term projects, it is only meant to be experimental, basically to test how arbitrary OpenType features will behave in browsers if you force these features, but it may not coexist well with standard renderers that normally use conditions (including user's accessibility settings) to use one feature or another, also depending on the font technology used. Consider "font-feature-settings:" to be transitional and to be used by testers (including developers of web renderers for various applications targetting various devices).Fishbowl
R
16

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>
Riki answered 13/4, 2012 at 23:23 Comment(8)
That's even more 'inside the box' (sorry, had to do it)Atypical
I think we should table this idea for now.Deshabille
You all should be locked in a cell for making these comments.Whiles
just like the CSS tricks splitting your string to separate each character into an inline block where you'll horizontally center each character, it looks very horrible, except when you use monospace fonts (and its's just better then to not use this splitting trick if you already use monospaced fonts, even when you use some additional "character-spacing").Fishbowl
Why not just reinvent the wheel and render the entire webpage on a canvas? I mean, it's the a sure way to get the text aligned perfectly across different browsers.Cowpuncher
For starters, the question was: "Force Non-Monospace Font into Fixed Width Using CSS"Solberg
At least spans should retain some screen reader accessibility, please nobody do this answer.Tercel
Not even joking: I love how going back to the '90s actually improves the situation. This is how bad the web has gotten. There's a reason manual type setting lasted 550 years...Mcfall
G
12

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>
Govea answered 13/4, 2012 at 23:4 Comment(6)
That really does look hideous.Antonantone
imo looks better with text-align: jsfiddle.net/f7TPf/3 but still is pretty hideousSverige
@Blender, In general you are correct, but there are cases however where a monospace font actually adds width for font attributes like bold an italic, and this can be bad if you need them to line up. Lucida Console is one such font. It makes it useless to use in an IDE if you want syntax highlighting to make use of bold, because Lucida Console 12pt is roughly 1 pixel wider when bold. Makes it impossible to line the code up via spaces (past the initial whitespace on a line). One IDE I found was smart enough to have a checkbox to lock the bold at the plain width.Plumbing
I know this is ancient but I feel the need to mention that there is a use for this, like in counters and icon fonts.Deceit
As far as I know, there's no way to expect that texts (or even just numbers) will have the same metrics if you change any font style (such as bold and italic) and in all resolutions, even when using fonts designed for an UI (on screen). Simply, many fonts don't support all ranges of styles and unrelated fonts may still be used (including for lack of coverage of the full set of encodable clusters you need).Fishbowl
CSS has no way to enforce any glyph to render in a predefined cellsize (not even in CJK, or with a generic "monospace" font family, generally ugly). You need to stick to one style, and add distinctions, by adding other notation separately from the figures (e.g. color, or extra icons or punctuation/symbol) if you want to add some emphasis on some figures (and place them appropriately to the right or left, depending on alignment side, or inside a separate data column).Fishbowl
N
12

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.
Nogood answered 30/12, 2018 at 18:54 Comment(4)
Only available in Firefox right now, but if it gets widespread support this will be the best answer.Rafaelrafaela
This won't work for many characters that are not encoded at all in Unicode with full-width form (the set of fullwidth variants that can be mapped by text-transform is very small, mostly ASCII only, and a few Asian punctuation and currency signss. Fullwidth variants are encoded in Unicode only for compatibility with legacy encodings, but the proper encoding is to use normal letters and styling text separately.Fishbowl
The usable subset is only: 0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z , . : ; ! ? " ' ` ^ ~  ̄ _ & @ # % + - * = < > ( ) [ ] { } ⦅ ⦆ | ¦ / \ ¬ $ £ ¢ ₩ ¥ So don't expect to render "café" correctly. And there's such no solution for Indic scripts, Arabic, Hebrew, Cyrillic, Thai...Fishbowl
This only works for very basic English, and does not even work for Romanized Japanese (absence of macron diacritic) or Romanized Chinese (absence of tone marks), or korean (even if there's a Won currency sign)Fishbowl
S
6

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.

Surplusage answered 27/1, 2017 at 12:44 Comment(1)
yes horrible, and equivalent visual effect when splitting the string using centered table cells, or inline-blocks, or javascript. But there are typografically correct ways to enhance the rendering in tabular data columns, notably for numbers (see below : no HTML trick, no javascripts, only one CSS property to define, plus possibly another CSS property to align the dots for non-integer numbers of variable precision and range)Fishbowl
U
0

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;}
}
Unsuccess answered 4/12, 2014 at 15:55 Comment(0)
E
0

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>
Envy answered 4/12, 2021 at 21:35 Comment(1)
Setting the width to near 0 is getting me on the way, thanks!Fraunhofer
H
0

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.

Hodden answered 24/3, 2022 at 19:51 Comment(2)
Use another font if you use any font with variable-width digits. Standard fonts in your modern browsers all have the support for fixed-width digits. If you still use proportional font, it will jsut be simpler then to make the text spans containing a number use a "font:monospace", without needing any Javascript and visual reflow after the page is loaded (reflows are a pain when navigating, you can't click reliably, you have to wait until the page is loaded and all scripts including the one above has run). Typical usage of tabular numbers in large data tables takes long to load and transform.Fishbowl
@Fishbowl of course this is a terrible hack, but sometimes you just gotta stick to some font - no excuses. sure thing nobody ever should do such thing on large tables, that'd be incredibly insane!Eisen
A
-2

A mix of answers from Márton Tamás and nïkö:

document.querySelectorAll('pre').forEach( o => {
  o.innerHTML = o.innerText.replace(/(.)/g, '<i>$1</i>');
});
pre i {
  font-style: normal;
  font-family: serif;
  display: inline-block;
  width: 0.65em;
  text-align: center;
}
<!-- Fixed-width serif -->
<pre>
. i I 1  | This is gonna be awesome. 12:10
ASDFGHJK | This is gonna be awesome. 08:51
</pre>

<!-- Default font -->
. i I 1  | This is gonna be awesome. 12:10
<br>
ASDFGHJK | This is gonna be awesome. 08:51
Angelenaangeleno answered 19/6, 2022 at 9:49 Comment(1)
This requires Javascript to transform the document content (slow reflow). Unfortunately, that script enumerate characters but breaks some unbreakable sequences in international texts. It is valid for western alphabets and simpel syllabaries, not good for Semitic abjads, Indic abugidas, and does not work well with CJK-based languages (that mix scripts and use different rules for the layout in "ideographic squares", including for the presentation of traditional numbers or Roman digits). Metrics are incorrect for South-East Asian scripts, breaking on Tibetan, Burmese, Khmer, Yi and Old Mongolian.Fishbowl
B
-3

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

Boorer answered 13/4, 2012 at 22:58 Comment(0)
B
-5

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.

Bondholder answered 13/4, 2012 at 23:0 Comment(2)
While this may seem like an important issue, my reason for disagreeing is equally!important.Cowpuncher
this isn't an answer, this is a commentRecept

© 2022 - 2024 — McMap. All rights reserved.