Appearance
Button Layout Stability with Dynamic Content
When to use
Use this pattern when button text changes between states — like “Copy” to “Copied!” — and the button must not shift layout. This pattern overlays all states in a single CSS Grid cell.
The pattern
The HTML structure places all state labels inside the button as sibling spans. Each span carries a data-state attribute and an aria-hidden toggle — only the active state is visible, while hidden states still contribute to the button's size.
html
<button
class="feedback-button"
type="button"
aria-live="polite"
>
<span data-state="idle" aria-hidden="false">
Copy
</span>
<span
data-state="success"
aria-hidden="true"
role="status"
>
<span role="presentation">✓</span>
Copied
</span>
</button>The CSS uses inline-grid so all state spans stack in the same cell. Every span is visibility: hidden by default, and the [aria-hidden="false"] selector reveals only the active one — keeping layout space for all states.
css
.feedback-button {
display: inline-grid;
place-items: center;
padding: 0.6em 1.2em;
}
.feedback-button [data-state] {
grid-column: 1 / -1;
grid-row: 1 / -1;
visibility: hidden;
display: inline-flex;
align-items: center;
gap: 0.4em;
}
.feedback-button [data-state][aria-hidden="false"] {
visibility: visible;
}State transitions swap aria-hidden between spans so the visible state changes without affecting layout. The aria-live="polite" on the button ensures screen readers announce the new state.
js
document.querySelectorAll(".feedback-button")
.forEach((btn) => {
const [idle, success] =
btn.querySelectorAll("[data-state]");
btn.addEventListener("click", async () => {
idle.setAttribute("aria-hidden", "true");
success.setAttribute("aria-hidden", "false");
await delay(1500);
success.setAttribute("aria-hidden", "true");
idle.setAttribute("aria-hidden", "false");
});
});How it works
- All states occupy the same grid cell
- Button auto-sizes to widest state content
- Toggle
aria-hiddento switch visibility visibility: hiddenpreserves layout space- Screen readers announce state via
aria-live
Common use cases
- Copy buttons: Copy → Copied!
- Save buttons: Save → Saving... → Saved!
- Submit buttons: Submit → Submitting... → Success!
- Toggle actions: Follow → Following