You should be familiar with box-sizing property which determines how the width and height are calculated, in chrome the default value for box-sizing set on a button is border-box
border-box: The width and height properties includes content, padding and border
Let's now talk about your case:
Content:
<button type="button" class="dot"></button>
The button has no text which means the width of it's content is 0px
Padding:
Default padding on the button applied by the user agent(chrome in this case) is
padding-top: 1px;
padding-right: 6px;
padding-bottom: 1px;
padding-left: 6px;
Border:
we only care about the width of the border
Default border on the button applied by the user agent(chrome in this case) is
border-top-width: 2px;
border-right-width: 2px;
border-bottom-width: 2px;
border-left-width: 2px;
we'll talk about the width alone, because it's pretty much same thing for the height
Without applying any styles, let's sum it all up
left border 2px + left padding 6px + content 0px + right padding 6px + right border 2px
So the overall width of a button by default will be 16px.
Now when you say width:9px you explicitly setting the width in which border+padding+content should live in, but their width is wider so it expands.
Why doesnt it happen on 15x15 tho.. (becomes 16x15 as if it has padding to fill till 16px on
That's because 16px is the exact amount of space border+padding+content need to live, that's why you see no change.
Now if you set the width to be wider than 16px what will change is the content's width because border get set width border property and padding width padding property.
Solution
So to fix this we need to reset those predefined styles padding and border
console.log(document.querySelector('button').getBoundingClientRect().width)
button {
width: 9px;
padding: 0;
border:none;
}
<button></button>