(Edit: I only need this for the 10 digits 0..9, can I hardcode the letter width for each one of the 10 digits in the CSS file?)
I have read CSS - Make sans-serif font imitate monospace font but the CSS rule letter-spacing
doesn't seem to be enough:
How to imitate a monospace fixed font from a standard sans-serif font?
This doesn't work perfectly:
.text {
font-family: sans-serif;
letter-spacing: 10px;
}
<div class="text">
ABCDEFGHIJKLMNOPQR<br>
STUVWXYZ0123456789
</div>
font-variant-numeric: tabular-nums
if you only need to get tabular numbersAll these hacks are actually way more expensive and "wobbly" with regards to a predictable cross-browser rendering. However let's summarize these hacks:
Apart from changing the actual font metrics in a font editor and compiling a new font you could split up all letters into an array of <span>
elements via javaScript.
emulateMonospace();
function emulateMonospace() {
let monoWraps = document.querySelectorAll(".toMonospace");
monoWraps.forEach(function(monoWrap, i) {
//remove all "\n" linebreaks and replace br tags with "\n"
monoWrap.innerHTML = monoWrap.innerHTML
.replaceAll("\n", "")
.replaceAll("<br>", "\n");
let text = monoWrap.textContent;
let letters = text.split("");
//get font-size
let style = window.getComputedStyle(monoWrap);
let fontSize = parseFloat(style.fontSize);
//find maximum letter width
let widths = [];
monoWrap.textContent = "";
letters.forEach(function(letter) {
let span = document.createElement("span");
if (letter == "\n") {
span = document.createElement("br");
}
if (letter == ' ') {
span.innerHTML = ' ';
} else {
span.textContent = letter;
}
monoWrap.appendChild(span);
let width = parseFloat(span.getBoundingClientRect().width);
widths.push(width);
span.classList.add("spanMono");
span.classList.add("spanMono" + i);
});
monoWrap.classList.replace("variableWidth", "monoSpace");
//get exact max width
let maxWidth = Math.max(...widths);
let maxEm = maxWidth / fontSize;
let newStyle = document.createElement("style");
document.head.appendChild(newStyle);
newStyle.sheet.insertRule(`.spanMono${i} { width: ${maxEm}em }`, 0);
});
}
body{
font-family: sans-serif;
font-size: 10vw;
line-height: 1.2em;
transition: 0.3s;
}
.monospaced{
font-family: monospace;
}
.letterspacing{
letter-spacing:0.3em;
}
.teko {
font-family: "Teko", sans-serif;
}
.serif{
font-family: "Georgia", serif;
}
.variableWidth {
opacity: 0;
}
.monoSpace {
opacity: 1;
}
.spanMono {
display: inline-block;
outline: 1px dotted #ccc;
text-align: center;
line-height:1em;
}
<link href="https://fonts.googleapis.com/css2?family=Teko:wght@300&display=swap" rel="stylesheet">
<h3 style="font-size:0.5em;">Proper monospace font</h3>
<div class="monospaced">
WiWi</br>
iWiW
</div>
<h3 style="font-size:0.5em;">Letterspacing can't emulate monospaced fonts!</h3>
<div class="letterspacing">
WiWi</br>
iWiW
</div>
<hr>
<h3 style="font-size:0.5em;">Text splitted up in spans</h3>
<div class="toMonospace variableWidth">
ABCDEFGHIJKLMNOPQR<br>
STUVWXYZ0123456789<br>
</div>
<div class="toMonospace variableWidth teko">
ABCDEFGHIJKLMNOPQR<br>
STUVWXYZ0123456789<br>
</div>
<div class="toMonospace variableWidth serif">
This is<br>
not a<br>
Monospace<br>
font!!!
</div>
Each character will be wrapped in a span with an inline-block
display property.
Besides all characters are centered via text-align:center
.
The above script will also compare the widths of all characters to set the largest width as the span width.
Admittedly, this is not very handy
but this approach might suffice for design/layout purposes and won't change the actual font files.
As illustrated in the snippet:
In monospace fonts the widest letters like "W" are designed in a kind of squeezed manner (not distorted)
whereas the thinner ones like
"i" are designed visually stretched (e.g by adding bottom serifs).
Fira Mono vs. Fira Sans – the mono style "i" resembles a "slab serif" – introducing serifs
So proper monospace fonts can't really be emulated.
Quite a few web fonts also include tabular numerals (sharing the same width) which can be applied via CSS property
font-variant-numeric: tabular-nums
We can specify a unicode-range in the @font-face
rule to apply a font only to numbers
body {
font-size: 10vw;
font-family: Arial;
}
h3{
font-family: sans-serif;
font-size:16px;
line-height:1.3em;
}
h4{
font-family: sans-serif;
font-size:12px;
line-height:1.3em;
}
@font-face {
font-family: ArialStd;
font-style: normal;
font-weight: 400;
src: local("Arial");
}
@font-face {
font-family: Arial;
font-style: normal;
font-weight: 400;
src: local("Arial");
}
/** numbers **/
@font-face {
font-family: Arial;
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/firasans/v17/va9E4kDNxMZdWfMOD5Vvl4jL.woff2) format("woff2");
unicode-range: U+30-39;
}
.arialStd{
font-family: ArialStd;
}
.firaNums{
font-family: Arial;
font-weight: 400;
}
.tabNum {
font-family: Arial;
font-weight: 400;
font-variant-numeric: tabular-nums;
}
<h3>Variable width numerals</h3>
<h4>No substitution</h4>
<p class="arialStd">
Arial 01111<br>
Arial 10000
</p>
<h3>Numbers replaced via unicode range - not tabular</h3>
<h4>Arial (numbers with Fira-Sans)</h4>
<p class="firaNums">
Arial 01111<br>
Arial 10000
</p>
<h3>Numbers replaced via unicode range - tabular</h3>
<h4>Arial (numbers by Fira-Sans)</h4>
<p class="tabNum">
Arial 01111<br>
Arial 10000
</p>
text-transform: full-width
This property normalizes rendered character-widths to a uniform value. This feature is currently only supported by Firefox.
body {
font-size: 10vw;
font-family: Arial
}
.tabNum {
font-weight: 400;
text-transform: full-width;
}
<h3>Only supported in Firefox</h3>
<p class="tabNum">
Arial 01111<br> Arial 1000
</p>
letter-spacing
doesn't workletter-spacing
just evenly inserts whitespace between all letters (...hence the name).
It won't normalize characters/glyphs to have the same widths.
We would need a css property like letter-width
which doesn't exist.
Most likely we'll never get a similar property as text nodes don't add selectable sub nodes for each character – this would be kind of an overkill.