Skip to main content

Solving ':focus-visible' Outline Clipping in Overflow-Hidden Bento Grids

 The "Bento" grid design trend—characterized by modular, card-based layouts with heavy corner rounding—has introduced a subtle but pervasive accessibility regression.

To achieve the signature Bento look, developers instinctively apply overflow: hidden to card containers. This ensures that background images, videos, or child elements strictly adhere to the parent's border-radius. However, this architectural decision creates a conflict with the CSS Box Model: standard focus outlines are rendered outside the border edge. When overflow is hidden, the browser clips the focus ring, rendering it invisible or partially cut off.

This results in a direct violation of WCAG 2.4.7 (Focus Visible) and often fails WCAG 2.2 SC 2.4.11 (Focus Appearance) due to insufficient contrast area.

Below is the root cause analysis and the architectural pattern to resolve this without compromising the design system.

The Root Cause: The Paint Order Conflict

The issue stems from how browsers calculate the clipping region. When you apply overflow: hidden and a border-radius to a container, the browser creates a clipping path at the padding edge (or border edge, depending on browser implementation nuances regarding borders).

Standard CSS outlines (outline-style) do not take up space in the DOM; they are painted during a post-layout pass, sitting outside the element's border box.

  1. Layout: The element is sized.
  2. Paint: The background and borders are drawn.
  3. Clip: The overflow: hidden mask is applied to the content box + padding.
  4. Outline: The outline attempts to draw outside the border.
  5. Conflict: Because the outline is attached to the element generating the overflow context, the clipping mask (which restricts visual output to the rounded corners) chops off the external outline.

Many developers attempt to fix this with outline-offset: -npx (moving the ring inside). While valid, this often obscures content or fails contrast checks against dark background images.

The Fix: Decoupling Interaction from Visualization

To solve this rigorously, we must separate the interactive context (which receives focus) from the visual context (which requires clipping).

We treat the focusable element (e.g., <a><button>) as a purely structural wrapper that allows overflow, while an inner container handles the aesthetic clipping.

The Implementation

Here is a component structure implementing the "Decoupled Container" pattern. This example uses raw HTML/CSS for clarity but maps 1:1 to React/Vue component structures.

HTML Structure

<section class="bento-grid">
  <!-- 
    The 'bento-card' is the interactive host. 
    It receives focus but does NOT hide overflow.
  -->
  <a href="/analytics" class="bento-card">
    
    <!-- 
      The 'bento-content' handles the visuals.
      It clips images/children to the border radius.
    -->
    <div class="bento-content">
      <img 
        src="analytics-graph.jpg" 
        alt="Weekly analytics showing an upward trend" 
        class="card-image"
      />
      <div class="card-meta">
        <h3>Analytics</h3>
        <p>+24% vs last week</p>
      </div>
    </div>
    
  </a>
  
  <!-- Additional cards... -->
</section>

CSS Architecture

:root {
  --radius-lg: 24px;
  --focus-ring-color: #3b82f6;
  --focus-width: 3px;
  --focus-offset: 4px;
}

.bento-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1.5rem;
  padding: 2rem;
}

/* 
  1. Interactive Container 
  - Sets the dimensions
  - OWNS the focus state
  - Does NOT hide overflow
*/
.bento-card {
  position: relative;
  display: block;
  text-decoration: none;
  border-radius: var(--radius-lg);
  color: inherit;
  
  /* Critical for accessible interaction targets */
  transition: transform 0.2s ease;
}

/* 
  2. The Focus Ring
  - We use :focus-visible to only target keyboard/assistive tech users
  - The outline is now free to render outside the element
*/
.bento-card:focus-visible {
  outline: var(--focus-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-offset);
  
  /* Optional: Ensure focused element sits above neighbors in the grid */
  z-index: 10; 
  
  /* Browser Reset: Remove default WebKit focus if necessary */
  -webkit-tap-highlight-color: transparent; 
}

/* 
  3. Visual Wrapper
  - Handles the masking (clipping)
  - Inherits the radius from the parent to ensure alignment
*/
.bento-content {
  position: relative;
  width: 100%;
  height: 100%;
  
  /* The culprit logic is isolated here, safely away from the outline */
  overflow: hidden;
  border-radius: inherit; 
  
  background: #1a1a1a;
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}

.card-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
  display: block;
  
  /* Smooth transform on hover without affecting focus ring */
  transition: transform 0.3s ease;
}

/* Interactive Hover State (Visuals only) */
.bento-card:hover .card-image {
  transform: scale(1.05);
}

.card-meta {
  padding: 1.5rem;
}

Why This Works

1. Separation of Concerns

By moving overflow: hidden to a child element (.bento-content), the parent anchor tag (.bento-card) maintains the default overflow: visible behavior (or effectively behaves as such regarding outlines). The browser paints the outline on the parent, which has no clipping mask active.

2. border-radius: inherit

A common mistake in this pattern is defining the border radius in two places. If the parent radius changes, the child remains the old shape, creating visual artifacts ("bleed"). Setting .bento-content { border-radius: inherit; } ensures the clipping mask always perfectly matches the interactive footprint of the parent.

3. Z-Index Stacking

In tight grid layouts, an expanded focus ring might bleed underneath a neighboring card if that neighbor has a higher stacking context. Adding z-index: 10 (or a locally scoped variable) specifically inside the :focus-visible pseudo-class ensures the active element pops to the top of the stacking order, guaranteeing the ring is never obscured by adjacent UI.

Conclusion

Accessibility fixes often fail when they fight the browser's rendering engine. Instead of trying to force an outline inside a clipped box or resorting to complex box-shadow hacks that don't respect Windows High Contrast Mode, use the DOM hierarchy to your advantage.

Isolate the Interactable from the Clippable. This pattern ensures your Bento grids remain visually sharp while passing rigorous accessibility audits.