3 Layers of UI Interaction
Itās no surprise, dear reader, that most web interactions are lacking, and few interfaces have the level of polish they should. Part of that polish comes from being mindful of what Iāll refer to as āThe 3 Layers of UI Interaction:ā
- Render area: where does this element appear?
- Hit area: what is the shape/size/placement of the invisible interactive area?
- Focus area: when Tab-ing, what is the focus ring shape/placement?
Weāll skip over the first layerārender areaābecause itās a given (if it exists, itās ārenderedā). While there are deeper nuances like affordances, thatās beyond the scope of this post. We only call out the first layer to point out that there are additional layers beyond this one.
Itās only once you realize the 2 other layers exist and are distinct from the first that youāll create UI so fluid and smooth it might as well be invisible to the user. We can explore how all 3 layers divergeāand require separate decisionsāin one simple example: a dialog ācloseā button.
Nothing you havenāt seen before. But before we get further, ask yourself: āHow would I code this? What pixels count as part of the close button (and conversely, which pixels donāt)? What does the focus ring look like?ā In fact, this may be something youāve built dozens of times without being aware of some of the decisions you could make.
In practice, youāve probably seen (or even built) naĆÆve implementations such asā¦
āBabyās first buttonā
This implementation flattens all 3 layers together, resulting in a frustrating user experience. This is a common trap that probably every frontend dev has fallen into at the start of their career (often because the rendering part proved such a challenge thereās no time or mental energy left for the deeper interactions).
āThe ableistā
This second implementation correctly separates Layer 1 (rendering) from 2 (hit area), resulting in a pleasant interaction for mouse & touchscreen users. But it falls short of the ideal solution because it forgets about keyboard users that need Layer 3: focus area.
Solution
While both of the previous implementations are functioning (barely), theyāre not ideal, which is what weāre after. The best solution is a bit more complex:
This is particularly annoying to code, because it will involve lots of tweaks to align everything properly:
.close-btn {
/* settings (adjust all to taste) */
--icon-size: 1rem;
--hit-area-ratio: 2;
--hit-area-offset: 0.25;
--focus-ring-ratio: 0.125;
/* rendering: align icon to corner */
display: flex;
align-items: flex-end;
justify-content: flex-start;
position: absolute;
right: 0;
top: 0;
/* hit area: oversize the interactive area (and offset the icon within) */
height: calc(var(--hit-area-ratio) * var(--icon-size));
padding-bottom: calc(var(--hit-area-offset) * var(--icon-size));
padding-left: calc(var(--hit-area-offset) * var(--icon-size));
width: calc(var(--hit-area-ratio) * var(--icon-size));
/* focus ring: position & alignment */
outline: none;
&::after {
border-radius: 50%;
bottom: calc(var(--hit-area-offset) * var(--icon-size) + var(--icon-size));
content: "";
left: calc(var(--hit-area-offset) * var(--icon-size) + var(--icon-size));
outline: 2px solid blue;
opacity: 0;
pointer-events: none;
position: absolute;
transform: translate3d(-50%, 50%, 0);
}
/* focus ring: visible on focus */
&:focus-visible::after {
opacity: 1;
}
}
Note: this is valid CSS now because CSS nesting is now widely available!
And of course, this extra work in execution is exactly the reason why most things lack this level of polish: time. But once you start considering how a UI elementās interactive properties (hit area + focus area) can be separated from its basic rendering, you begin to unlock new levels of perfection in UI interactions, better accessibility, and new levels of delight.
Further Reading
While this approach has been in my work for years, it didnāt occur to me to write it down until I read Matthew Strƶmās post The polish paradox: the more you polish, the less you see. While Matthewās post is less pragmatic and more a general overview, itās a fantastic, evergreen summary of the overarching principles of UI interaction Iāll be referring to and linking to for years. Itās a perfect example about the highest levels of thoughtful interactions that many people new to frontend donāt even realize happen even in websites they frequent weekly (with my favorite example being Amazonās hover menu tunnel which is mentioned in the post).