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:
The ideal solution involves the correct hit area around the close icon (shown as a dashed red line). We donât want it to be too large so it doesnât overlap text content a user may want to highlight. But we also want to prevent dead space, so we asymmetrically extend the hit area into the corner because we can assume any interaction there is an intent to close.
The focus ring being a tighter circle around the icon shows this interaction got equal time and consideration as the others, and shows keyboard users you care about them (note the circle shape is a design choice that doesnât matter, but the attention to detail here does).
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).