All four labels live in the same grid cell, and only the active one is visible. That makes the button as wide as its longest label, so it never changes size when the state changes. Changing width mid-click is a classic cause of mis-taps.
The whole state machine is one attribute, data-state, which CSS reads to pick the label and color. The click handler ignores presses while busy, so double clicks don't send two requests. aria-busy and a role="status" line keep screen-reader users informed.
Use it for real: replace fakeSave() with your fetch() call.