A disabled button drops out of the tab order, so keyboard users skip right past it and screen readers stay silent. The block exists, but nothing announces it.
Pointer events are dead on a disabled element, so a tooltip meant to explain the block never fires. The reason is unreachable by design.
Greyed-out labels usually fail contrast. A disabled state can land near 1.9:1, well under the 4.5:1 threshold, so the text is hard to read on top of being blocked.
Keep the button live and validate on click instead. Light up the fields that are blocking submit, then move focus to the first one so the path forward is visible.
Disabled and loading are different states. During a request, hold focus, show a spinner, and report aria-busy; greying the button out throws the user's place away.
Do / Don't
Do: Keep the button enabled, validate on click, then flag the blocking fields and move focus to the first one.
Do: For async actions, use a busy state that holds focus, spins, and sets aria-busy.
Do: Name the blocker in reachable text, not a tooltip attached to a dead control.
Don't: Disable submit and leave the user to guess what is missing.
Don't: Rely on a tooltip to explain a disabled control, since pointer events never fire on it.
Don't: Treat loading as disabled; greying out mid-request drops focus and the user's place.
The Design System Blueprint
An 11-page visual guide to consistent, scalable UI: colors, typography, spacing, components, motion.