HomeJournalThis post

Foundations: interaction states beyond hover

A useful component system defines state as product behavior: focus, loading, disabled, selected, invalid, pressed, and unavailable.

JP
JP Casabianca
Designer/Engineer · Bogotá

Interaction states are where design systems become real. A component can look polished in its default state and still fail the product the moment someone tabs to it, submits it, waits on it, or hits an error.

Hover is the easiest state to design because it is visual and familiar. It is also the least complete.

Start with the state list

For interactive components, I want the state list written before the final visual treatment:

  • Default.
  • Hover.
  • Focus.
  • Active or pressed.
  • Selected.
  • Loading.
  • Disabled.
  • Invalid.
  • Read-only.
  • Unavailable.

Not every component needs every state, but the team should decide intentionally. Missing states become product bugs later.

Focus is not hover

Focus is a navigation state, not a decoration. It tells keyboard and assistive technology users where they are. It should be visible, consistent, and hard to confuse with selection.

I prefer focus styles that sit outside the component shape when possible. A clear ring survives color changes, dense layouts, and complex components better than a subtle tint.

Do not remove focus because it looks loud in screenshots. Screenshots are not the user experience.

Disabled should explain itself nearby

Disabled controls are often a dead end. They prevent action but do not explain what is missing.

Sometimes disabled is right: a submit button while a form is invalid, a billing action without permission, a destructive action during loading. But the reason should be visible nearby. If the user has to guess why a button is disabled, the interface is withholding information.

For critical actions, I often prefer an enabled button that opens a clear explanation over a silent disabled state.

Loading is a state of trust

Loading buttons, menu items, table rows, and cards should preserve shape. The product should not jump because a label changed from "Save changes" to a spinner.

A loading state should answer three questions: is the action in progress, can I cancel or leave, and what happens if it fails? Small components cannot answer all of that alone, but the surrounding UI can.

Invalid is not a punishment

Validation should help correction. The invalid state needs a visual cue, a message, and a path back to success.

Color alone is not enough. A red border without text is decoration. A message far from the field is a memory test. A good invalid state says what changed, where to fix it, and preserves the user's input.

Selected and active are different

Selected means this item is currently chosen. Active or pressed means the user is currently interacting with it. Mixing them creates confusing tabs, segmented controls, menus, and list items.

Write the meaning down in the component docs. The visual system follows the product language.

The state review

Before a component leaves the system, run it through:

  • Mouse.
  • Keyboard.
  • Touch.
  • Slow network.
  • Empty data.
  • Invalid input.
  • Permission limits.
  • Reduced motion.

That review is not extra polish. It is the component behaving like product infrastructure.

State is product language

Interaction states are often documented as visual variants: hover color, disabled opacity, focus ring, loading spinner. That is necessary, but incomplete. A state is product language. It tells the user what is possible, what is happening, what is selected, what failed, and what they can do next.

When a design system treats states as decoration, product teams fill the gaps locally. One team makes disabled buttons lighter. Another adds tooltips. Another keeps buttons enabled and shows alerts. Another hides unavailable actions. None of those choices are automatically wrong, but inconsistency spreads because the system did not define the behavior.

NavigationFocus

Where am I, and what will activate if I press Enter?

ProgressLoading

Is the system working, and can I safely act again?

RecoveryInvalid

What changed, where is it wrong, and how do I fix it?

Figure 1: States answer product questions. They are not just visual effects.

Define meaning before styling

Before choosing colors or motion, I want the team to define what each state means.

For example:

  • Disabled: the action cannot be taken yet because a requirement is missing.
  • Loading: the action has been requested and the system is waiting for a result.
  • Selected: this option is currently chosen.
  • Active: this control is currently being pressed or activated.
  • Invalid: the entered value cannot be accepted and needs correction.
  • Read-only: the value is visible but cannot be changed by this user.
  • Unavailable: the feature exists but is blocked by plan, permission, or system status.

That list prevents several common mistakes. Disabled and unavailable are not the same. Selected and active are not the same. Read-only and disabled are not the same. Invalid and error are not always the same.

The visual system should follow the meaning.

Focus needs its own respect

Focus is the state I check first because it reveals whether the system treats keyboard users as first-class users. A focus style should be visible, consistent, and not dependent on hover.

I prefer focus rings that sit outside the component because they survive dense layouts and color changes. If the ring is too subtle for screenshots, it is probably too subtle for actual use.

The docs should say:

  • Which elements receive focus.
  • What happens when a composite component opens.
  • Where focus goes after close.
  • How arrow keys behave in menus, tabs, and listboxes.
  • Whether focus is trapped in dialogs.

That is product behavior. It belongs in the component contract.

Default Focus Selected A state system should show operation, not only decoration.
Figure 2: Default, focus, and selected can look related, but they need distinct meanings and affordances.

Disabled should not hide the path

Disabled controls are overused. They stop action but often hide the reason. If a primary CTA is disabled and the user cannot tell why, the interface is making them debug the product.

For forms, disabled can work when requirements are visible. For permission or plan limits, I often prefer an enabled action that opens an explanation. That way the user learns what is missing.

The design system should document this decision:

  • Use disabled for incomplete local requirements.
  • Use unavailable for permission, plan, or system limitations.
  • Use read-only when a value is intentionally visible but not editable.
  • Use inline reasons when the next step is not obvious.

This distinction reduces local improvisation.

Loading should preserve trust

Loading states should preserve size and context. A button that changes width when it loads creates layout noise. A table that disappears during refresh creates doubt. A chart that clears completely makes the user wonder whether the data is gone.

Good loading states answer:

  • What is waiting?
  • Can I interact with anything else?
  • Can this action duplicate?
  • What happens if it fails?

For design systems, the key is not one universal spinner. It is guidance for where loading belongs: button, row, panel, page, background sync, or inline field.

The docs matrix

For any component with interaction, I want a state matrix:

State Meaning Visual cue Behavior Copy
Focus Current keyboard target ring Enter activates none
Loading Request pending spinner or label prevent duplicate action-specific
Invalid Correction needed border and message preserve value explain fix
Unavailable blocked by external condition muted plus reason explain path name blocker

This matrix makes review easier. QA can test it. Engineers can implement it. Designers can see where states conflict.

DesignShows the state

Visual cue is distinct and fits the component family.

CodeEnforces behavior

Duplicate submits, focus traps, and invalid inputs are handled.

CopyExplains recovery

The message says what happened and what to do next.

Figure 3: Strong states are shared work across design, code, and copy.

The foundation

If a design system gets interaction states right, product teams move faster with less drift. They do not have to reinvent disabled logic, focus treatment, loading behavior, or validation copy every sprint.

That is why I consider states foundational. They are where the component stops being a picture and starts behaving like product infrastructure.

A component-by-component pass

When I audit a design system for state coverage, I do not start with the prettiest components. I start with the components that carry consequence:

  • buttons
  • links
  • inputs
  • selects
  • menus
  • dialogs
  • drawers
  • tabs
  • tables
  • toasts
  • banners
  • command palettes

For each component, I ask the same questions:

  • What states exist?
  • Which states are visual only?
  • Which states require behavior?
  • Which states require copy?
  • Which states need keyboard rules?
  • Which states are missing from docs?
  • Which states have product exceptions?

The audit usually finds two kinds of gaps. The first is a missing visual treatment, which is easy to fix. The second is a missing product rule, which is more important. For example, the system may have a disabled button style but no rule for whether disabled buttons need explanatory text. That gap will create inconsistency across every product surface.

I also look for state collisions. A selected tab can also be focused. A loading button can also be disabled. A table row can be selected and invalid. The system needs to define how combined states behave, or teams will improvise.

This is where design-system maturity shows. Not in how many components exist, but in how reliably those components behave under pressure.

The release bar

I would not call a new component ready just because its default state is approved. For interactive components, ready means the state matrix is documented, keyboard behavior is known, and at least one product example has tested the edge states.

That sounds strict, but it saves time later. The first product team to use a component should not be responsible for discovering every missing behavior. The system should carry that work forward.

The payoff is consistency that users can feel. Buttons submit predictably. Menus focus predictably. Errors recover predictably. The interface starts to feel calm because the states have a shared language underneath them.

Companion artifacts

Use this after reading.

Practical downloads and templates that turn the article into something you can bring into a product review, implementation pass, or agent workflow.

DownloadJun 2026

Design System Contribution Pack

A contribution brief, drift diagnosis, escape-hatch rules, and component-docs template for product teams.

Design systemsComponentsDocs
View details
DownloadJun 2026

Front-End State Recipes

Reusable recipes for optimistic actions, loading, empty, error, data-transition, and disabled-control states.

FrontendStatesUX
View details