Skip to main content

Interaction states

Interaction states define how UI elements visually respond to user actions: hover, focus, active (pressed), and disabled. Consistent interaction states help users understand what's clickable, what's focused, and what's unavailable.

The 4 core states

StateTriggerPurpose
HoverMouse over elementIndicates interactivity
FocusKeyboard navigation / clickShows which element is active
ActiveMouse down / pressingConfirms the click is registering
DisabledElement unavailablePrevents interaction

State patterns by component

Buttons

StateVisual changeTailwind classes
HoverBackground darkens 10%hover:bg-primary/90
FocusRing appears (keyboard only)focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
ActiveScale down + darkenactive:bg-primary/80 active:scale-[0.98]
DisabledOpacity 50% + cursordisabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none

Key rule: Use focus-visible instead of focus for buttons. This shows the focus ring only for keyboard users, not mouse clicks.

Inputs

StateVisual changeTailwind classes
HoverBorder darkenshover:border-muted-foreground/50
FocusRing appearsfocus:ring-1 focus:ring-ring focus:outline-none
DisabledMuted background + opacitydisabled:opacity-50 disabled:bg-muted disabled:cursor-not-allowed

Key rule: Inputs use focus (not focus-visible) because users need to see which field is active at all times.

Interactive cards

StateVisual changeTailwind classes
HoverLift + shadowhover:shadow-md hover:-translate-y-0.5 transition-all
FocusRing appearsfocus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
ActiveDepressactive:scale-[0.99] active:shadow-sm

Key rule: Non-interactive cards should have NO hover effect. This avoids false affordance.

Links

StateVisual changeTailwind classes
HoverUnderlinehover:underline
FocusRing appearsfocus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2

Global rules

1. Hover feedback should be immediate

Hover states need instant feedback (under 50ms). Use transition-colors with short duration.

// Good — fast transition
className="transition-colors duration-50 hover:bg-primary/90"

// Bad — too slow
className="transition-all duration-300 hover:bg-primary/90"

2. Use focus-visible for keyboard focus

For buttons, links, and cards, use focus-visible to show focus rings only for keyboard navigation.

// Good — keyboard users only see ring
className="focus-visible:ring-2 focus-visible:ring-ring"

// Bad — mouse users see ring on every click
className="focus:ring-2 focus:ring-ring"

3. Disabled states need three things

  1. Visual indicator: disabled:opacity-50
  2. Cursor feedback: disabled:cursor-not-allowed
  3. Prevent interaction: disabled:pointer-events-none
// Complete disabled state
className="disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none"

// Incomplete — hover effects still work
className="disabled:opacity-50 disabled:cursor-not-allowed"

4. Keep hover effects subtle

Hover changes should be 5-10% opacity/lightness shifts, not dramatic transforms.

// Good — subtle
className="hover:bg-primary/90"  // 10% darker

// Bad — too dramatic
className="hover:scale-110 hover:rotate-2"

MCP tool usage

Getting interaction states

// Get all states for a button
get_interaction_states({ component: 'button' })

// Get specific state
get_interaction_states({
  component: 'button',
  variant: 'primary',
  state: 'hover'
})

// Include rules for learning
get_interaction_states({
  component: 'input',
  includeRules: true
})

Validating implementation

// Check if a component has all required states
validate_interaction_states({
  code: '',
  component: 'button'
})

// Returns missing states, violations, and recommendations

Common mistakes

Using focus instead of focus-visible

// Problem: Ring shows on mouse click


// Solution: Ring only on keyboard focus

Incomplete disabled states

// Problem: Hover still works when disabled


// Solution: Disable pointer events

Static cards with hover effects

// Problem: Card looks clickable but isn't

Content

// Solution: No hover on non-interactive elements

Content

FAQ

When should I use focus vs focus-visible?

  • focus-visible: buttons, links, cards, tabs (mouse users don't need focus indication)
  • focus: inputs, textareas, selects (users need to see which field is active)

Should all interactive elements have all four states?

Not necessarily:

  • Buttons: hover, focus-visible, active, disabled
  • Inputs: hover, focus, disabled (no active needed)
  • Links: hover, focus-visible (active optional)
  • Static cards: none (they're not interactive)

How dark should hover be?

10% is the sweet spot. Use /90 opacity in Tailwind:

  • hover:bg-primary/90 (10% darker)
  • hover:text-primary/90 (10% darker text)

Going darker (e.g., /70) is too dramatic for most cases.

Was this page helpful?

Last reviewed by @jschuyler
All systems operationalDocsDevelopersPlatformPricing
PrivacyTerms© 2026 fndd, LLC