This is a very old question, but I'm revisiting it because I had this problem in an app I'm developing and found all of the answers here wanting.
(Skip this paragraph for the TL;DR...) I'm using the Gotham webfont from cloud.typography.com, and I have buttons which start hollow (with a white border/text and a transparent background) and acquire a background color on hover. I found that some of the background colors I was using didn't contrast well with the white text, so I wanted to change the text to black for those buttons, but — whether because of a visual trick or common anti-aliasing methods — dark text on a light background always appears to be lighter weight than white text on a dark background. I found that increasing the weight from 400 to 500 for the dark text maintained almost exactly the same "visual" weight. However, it was increasing the button width by a tiny amount — a fraction of a pixel — but it was enough to make the buttons appear to "jitter" slightly, which I wanted to get rid of.
Solution:
Obviously, this is a really finicky problem so it required a finicky solution. Ultimately I used a negative letter-spacing on the bolder text as cgTag recommended above, but 1px would have been way overkill, so I just calculated exactly the width I would need.
By inspecting the button in Chrome devtools, I found that the default width of my button was 165.47px, and 165.69px on hover, a difference of 0.22px. The button had 9 characters, so:
0.22 / 9 = 0.024444px
By converting that to em units I could make the adjustment font-size agnostic. My button was using a font size of 16px, so:
0.024444 / 16 = 0.001527em
So for my particular font, the following CSS keeps the buttons exactly the same width on hover:
.btn {
font-weight: 400;
}
.btn:hover {
font-weight: 500;
letter-spacing: -0.001527em;
}
With a little testing and using the formula above, you can find exactly the right letter-spacing value for your situation, and it should work regardless of font size.
The one caveat is that different browsers use slightly different sub-pixel calculations, so if you're aiming for this OCD level of sub-pixel-perfect precision, you'll need to repeat the testing and set a different value for each browser. Browser-targeted CSS styles are generally frowned upon, for good reason, but I think this is one use case where it's the only option that makes sense.