This is a very old question, but I am returning to it because I had this problem in the application under development, and I found all the answers here.
(Skip this paragraph for TL; DR ...) I use the Gotham web font from cloud.typography.com and I have buttons that start blank (with a white border / text and a transparent background) and get the background color on hover . I found that some of the background colors that I used did not contrast well with the white text, so I wanted to change the text to black for these buttons, but - whether due to a visual trick or common anti-aliasing methods - dark text on a light background always looks lighter than white text on a dark background. I found that increasing the weight from 400 to 500 for dark text supports almost the same “visual” weight. Nevertheless, it increased the width of the button by a tiny amount - a fraction of a pixel - but this was enough to make the buttons “tremble” slightly, which I wanted to get rid of.
Decision:
Obviously, this is a really difficult problem, so it required a complex solution. I ended up using negative letter-spacing in the text in a bold font, as recommended by cgTag above, but 1px would be superfluous, so I just calculated exactly the width that I would need.
Having studied the button in Chrome devtools, I found that the default width of my button is 165.47 pixels, and when hovering it is 165.69 pixels, that is, the difference is 0.22 pixels. The button had 9 characters, which means:
0.22 / 9 = 0.024444 pixels
By converting this to em units, I could make the font size setting independent. My button used a font size of 16 pixels, so:
0.024444 / 16 = 0.001527em
So for my particular font, the following CSS maintains the same button widths on hover:
.btn { font-weight: 400; } .btn:hover { font-weight: 500; letter-spacing: -0.001527em; }
Having tested a little and using the above formula, you can find the exact letter-spacing value for your situation, and it should work regardless of the font size.
The only caveat is that different browsers use slightly different subpixel calculations, so if you strive for this OCD level with perfect subpixel accuracy, you will need to repeat the test and set different values for each browser. CSS styles targeting the browser are usually not approved , and there is a good reason for this, but I think this is the only use case when it is the only option that makes sense.