5.8 KiB
WebKit Tap Highlight and Border-Radius Issues on Mobile
Problem
Buttons and interactive elements lose their border-radius styling when tapped on mobile devices (iOS Safari, Chrome on iOS). The element briefly shows a rectangular highlight instead of respecting the rounded corners.
Symptoms:
- Button appears with sharp corners during tap/touch
- A blue or gray rectangular overlay flashes on touch
- The visual glitch only occurs on WebKit-based mobile browsers
- Desktop browsers and Android Chrome don't show the issue
Root Cause
WebKit browsers apply a default tap highlight effect to interactive elements. This highlight:
- Ignores
border-radius- The highlight is applied as a simple rectangular overlay - Uses system default color - Typically a semi-transparent blue or gray
- Overrides visual styling - The highlight appears on top of your custom styles
Technical Details
When you tap an element on iOS Safari:
/* WebKit's default behavior (pseudo-representation) */
element:active {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
/* This creates a RECTANGULAR overlay, ignoring border-radius */
}
The tap highlight is rendered as a separate layer that doesn't respect the element's border-radius, clip-path, or other shape-defining properties.
Solution
Solution 1: Disable Tap Highlight + Wrapper with Overflow Hidden
The most reliable solution combines two techniques:
// Button component with proper mobile touch handling
function Button({ children, className, ...props }: ButtonProps) {
return (
<div className="rounded-lg overflow-hidden inline-block">
<button
className={cn("rounded-lg px-4 py-2 bg-blue-500 text-white", className)}
style={{ WebkitTapHighlightColor: "transparent" }}
{...props}
>
{children}
</button>
</div>
);
}
Why this works:
WebkitTapHighlightColor: "transparent"removes the default highlight- The wrapper
divwithoverflow-hiddenclips any remaining visual artifacts - Both elements have matching
border-radiusfor consistent appearance
Solution 2: CSS-Only Approach
If you can't modify the component structure:
/* In your global CSS */
.tap-safe {
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
/* Custom active state to replace the highlight */
.tap-safe:active {
opacity: 0.8;
transform: scale(0.98);
}
<button className="tap-safe rounded-lg px-4 py-2 bg-blue-500">
Click me
</button>
Solution 3: Tailwind CSS Utility Class
Add a reusable utility in your Tailwind config:
// tailwind.config.js
module.exports = {
theme: {
extend: {},
},
plugins: [
function({ addUtilities }) {
addUtilities({
'.tap-highlight-none': {
'-webkit-tap-highlight-color': 'transparent',
},
});
},
],
};
Then use it in components:
<button className="tap-highlight-none rounded-lg px-4 py-2">
Click me
</button>
Solution 4: Wrapper Component for Consistent Behavior
Create a reusable wrapper for all interactive rounded elements:
// components/ui/touch-safe-wrapper.tsx
interface TouchSafeWrapperProps {
children: React.ReactNode;
className?: string;
borderRadius?: string;
}
export function TouchSafeWrapper({
children,
className,
borderRadius = "rounded-lg"
}: TouchSafeWrapperProps) {
return (
<div className={cn(borderRadius, "overflow-hidden inline-flex", className)}>
{children}
</div>
);
}
// Usage
<TouchSafeWrapper>
<button
className="rounded-lg px-4 py-2 bg-blue-500"
style={{ WebkitTapHighlightColor: "transparent" }}
>
Click me
</button>
</TouchSafeWrapper>
Complete Example: Card with Clickable Areas
function ProductCard({ product }: { product: Product }) {
return (
<div className="rounded-xl border p-4">
<h3>{product.name}</h3>
<p>{product.description}</p>
{/* Action buttons with tap-safe handling */}
<div className="flex gap-2 mt-4">
<div className="rounded-lg overflow-hidden">
<button
className="rounded-lg px-4 py-2 bg-blue-500 text-white"
style={{ WebkitTapHighlightColor: "transparent" }}
onClick={() => addToCart(product)}
>
Add to Cart
</button>
</div>
<div className="rounded-lg overflow-hidden">
<button
className="rounded-lg px-4 py-2 border border-gray-300"
style={{ WebkitTapHighlightColor: "transparent" }}
onClick={() => viewDetails(product)}
>
Details
</button>
</div>
</div>
</div>
);
}
Key Takeaways
-
WebKit tap highlight ignores border-radius - This is browser behavior, not a CSS bug
-
Always set
WebkitTapHighlightColor: "transparent"on interactive elements with rounded corners -
Use a wrapper with
overflow-hiddenfor the most reliable visual clipping -
Test on actual iOS devices - Simulators and browser dev tools may not reproduce the issue
-
Consider adding custom active states to replace the removed tap feedback for better UX
-
Create reusable components that handle mobile touch behavior consistently
Browser Support Notes
| Browser | Needs Fix |
|---|---|
| iOS Safari | Yes |
| Chrome on iOS | Yes (uses WebKit) |
| Firefox on iOS | Yes (uses WebKit) |
| Android Chrome | Usually no |
| Desktop browsers | No |