HTML Interactivity & States

The complete and simple guide to interactive element states

Uri Kutner
7 min readJul 12, 2023

Interactions are little signs that tell your user — “hey! you are interacting with an element. Your actions have consequences”. They empower visitors, and guide them through the things you want them to accomplish.

The many interactions of a button. Live demo here!

And yet, many websites and elements don’t bother making any interactions at all. It’s rather sad really, because these little interaction are simple to add, and makes things a lot more intuitive for the user.

When to use interactions?

This one is more simple than you’d think. If the user can interact with the element, the element should respond. Newton’s first law, or something.

Ain’t that simple? Now, let’s see it in code:

function Button({ onClick, interactive = !!onClick, ...props }) {
const classes = [props.className, interactive && interactiveStyles]
.filter(x => !!x)
.join(" ");

return <button {...props} onClick={onClick} className={classes}/>;
}

I find having onClick is a good enough indicator that an element has an action, though it can be enabled manually.

How to signal interactivity?

You can tell if an interaction is good, if it will work in a movie scene.

If Tom Cruise would, could you?

If a movie audience can understand what’s happening without thinking too hard, then your users should be able to do the same with your product, and even bystanders just sitting nearby. This can make your application intuitive, pleasant, and a beacon of quality that everyone will want to use. Basically, it will be Steve Job level product. 😎

The rule is rather simple:

if an element is actionable, it should be prominent.
This is particularly true when the user is actively interacting with the element!

While you want to minimize the overall attention grabbing, you do want to draw the user’s attention to actionable items, as actions have consequences. If you don’t want the clutter, just remove the element — tuck it in a menu if you have to.

We have a couple of tools for this in our utility belt. To draw attention to actionable elements, we can leverage the human eye’s sensitivity to motion, change, contrast, size, etc.
I will delve into these factors in a separate article.

What interactions should we have?

Let’s start with the obvious:

.interactive {
cursor: pointer; // duh
user-select: none; // to prevent accidental copying. use with care
}

.interactive:disabled {
cursor: not-allowed; // opinionated, but quite common
}

Then, you should make sure every way the user can interact is be visually represented. Just follow this check list! 🙂

:resting

This is the state where you element starts, and what designers always provide. Remember, even a resting item should be more prominent than regular elements, because it is hinting actionability.

Initial state — no animation

:hover

If a user can click on a button, he should know that BEFORE clicking on it.
This behavior might be very important in VR devices 😉.

Some older touch devices used to emulate :hover when pressing on the element, and then require a second press to actually activate it.
If this is a concern for you, consider disabling :hover styles in mobile. (You can use special media queries, but it might be easier to add a special class to the root).

Interactive element when hovered
“click me Seymour! 🪴”

I would discourage moving the element during the hover effect, (ie by using transform: translate() or shrink using scale()), because it can move the element away from the cursor (in some edge cases), and cause visible gitter.

:active (clicking, pressing)

This one is pretty straightforward, reacting to the user action, but I think the hidden message here is to remind the user he can still disengage.

In this active effect, I added a shake animation after a delay, to let the user know the button is still being pressed. After holding down the button, I disengage by moving the cursor outside. This can also be done when navigating by keyboard by jumping to the next element.

:focus (keyboard navigation)

Precise cursor or touch controls are not the only way to navigate the app. many users opt to use they keyboard for its efficiency, especially around forms, and some are forced to use joystick inputs like in car multimedia or TV remotes.

For some users, a joystick controller is the only option

Historically, Browsers have been using outline, a kind of a second border that marks the focused element. Some people hate it, and in very rare edge cases it can get really ugly, which is why some developer choose to remove it altogether, which is a big no-no.
I used square corners and a shadow, in addition to the built-in outline:

keep your eyes on the ball

You can use the :focus-visible selector, which will only show up when actually using the keyboard, or for any element which supports keyboard input (like a text input). Browser support is limited, refer to this css trick:

button:focus { /* some exciting button focus styles */ }
button:focus:not(:focus-visible) {
/* undo all the above focused button styles
if the button has focus but the browser wouldn't normally
show default focus styles */
}
button:focus-visible { /* some even *more* exciting button focus styles */ }

In this example, I left out the default outline behavior, which seems quite nice, so it will still work in older browser without additional code.

:disabled

Disabling an element signifies that it exists (duh), but cannot yet be interacted with. We can easily indicate that with a simple 0.38 opacity, and possibly a loss of saturation (graying it out), which lowers its prominence (contrast) and “vitality”.

Remember to undo other interactions styles when disabled!

Non standart interactions

While not standard, I also like having these interactions:

Loading

This is an easy win — for long asynchronous actions, we can show a loader to hint that the interaction is still ongoing. Very intuitive for the user, and it alleviates the need for a separate spinner.

You can use the promise from the action (ie, the return value of the onClick) to know how long the action is running.
It seems wise to also (optionally) disable the element while loading.

:checked (“activated” or on/off)

Often referred to as “on/off”, :checked is more common than you’d expect. Think of a “shuffle” button, or a search filter.

Shuffle button in Spotify has on/off states

Ideally, you should implement it with a hidden input and a label, but you might get away with role="switch" and aria-checked="true.
Remember, not all users will perceive visual cues, so using a real checkbox (even if hidden) or the aria/role properties will make a big difference.

When it comes to naming, I have found two options: checked, which is more like the standard html, and on={boolean}, which is more intuitive.
Interestingly enough, MaterialUI v2 had “selected”, “activated”, and “on/off” states, but later streamlined to just “activated” in v3 — minimalism wins, I guess.
I find that activated is more confusing than helpful, because some people mistake it for the :active pseudo-selector.

“Wax on, wax off 🥋🧽”

In this case, I’ve used ● (full dot) and ○ (empty dot) to signify the state, with an added touch of inset shadow for depth. Ideally, the two states should be symmetrical in design.

Remember, this is just one approach, and your design could vary wildly depending on the use case.

Dragged

Dragging is a less common interaction, though it is still prevalent. Styling will adhere to your specific use case, though it is common to include a handle (to hint of draggability), and use an elevation effect with shadows while dragging.

Error and Success

Another easy win is a built-in success and error states. They effectively communicate that the action started by this element has come to completion or failure, and frees the developer from additional feedback.

I used a state machine to show a success message when the action promise is resolved, and a error message when it is rejected. The success message goes away automatically, the error requires another click to reset.
I used Intents (see separate article here) to color the element, though it needs a little bit more tinkering (for example, a danger button shouldn’t turn green “success” when it completes. Maybe neutral gray instead)

What’s next?

I had a lot of fun researching this paper, playing around with states, and refining my experience from the last 11 years. I hope you liked it too, and of course, if I missed anything, let me know of your insights.

You can find all of the source code here. Have fun!
https://github.com/KutnerUri/demo-interactions

Want a live demo? no problem!
https://kutneruri.github.io/demo-interactions/

--

--