Three layers of CSS to hide a 2-pixel iOS Safari bug
6 min readThe reCAPTCHA widget on the contact form looked correct in every browser I checked. Chrome, Firefox, Edge, desktop Safari, Android Chrome. Then I opened the site on my phone, in dark mode, in Safari, and there it was: a thin bright rectangle framing the iframe, like someone had drawn a 1-pixel highlighter around it.
One device. One browser. One color theme. The classic shape of a bug you will spend an afternoon on.
What it actually looked like
The widget itself rendered fine. The checkbox, the I'm not a robot label, the Google logo, all where they should be. But there was a faint warm-grey halo running the full perimeter of the iframe, slightly more pronounced at the bottom-right corner. Against the near-black surface of the contact card it read as a glowing rectangle. On a light background you would never notice. In dark mode it was the only thing my eye went to.
The widget sits inside a cross-origin iframe, which means I cannot open dev tools, click inspect, and find the offending CSS rule. The iframe is Google's. I get to style around it; I do not get to style into it.
What I tried first (and why it did not work)
My first guess was a border. Iframes pick up a 1px border by default in some user-agent stylesheets. So I dropped a border: 0 on the iframe. No change.
My second guess was a box-shadow on the wrapper. reCAPTCHA injects its own div between the element you mount it on and the iframe, and Google's CSS adds an inline box-shadow to that wrapper for elevation. I overrode it with box-shadow: none. The corners got better. The bottom-right corner specifically got a lot better. But there was still a thin edge.
My third guess was that the iframe's own rounded corners were anti-aliasing against my dark wrapper, showing a few sub-pixels of background through the curve. I added overflow: hidden and a matching dark background on the wrapper. The straight edges cleaned up. The corners almost cleaned up. Almost.
The remaining 1px of bright was inside the iframe. Google's dark-theme reCAPTCHA paints its own subtle border on the inside of the iframe. Cross-origin. Untouchable. I sat there for a minute trying to figure out how to override CSS I had no access to.
Layer 1: kill the wrapper's decorative chrome
Each fix on its own only addressed part of the problem, so the final solution stacks all three. Start with the wrapper Google injects.
.contact__captcha .g-recaptcha,
.contact__captcha .g-recaptcha *:not(iframe) {
border: 0 !important;
box-shadow: none !important;
outline: none !important;
}
The :not(iframe) part matters. I want to nuke borders and shadows on the wrapper Google injects, not on the iframe element itself. Selecting the iframe in this rule would not do anything anyway (cross-origin), but being explicit makes the intent obvious to whoever reads this six months from now.
Layer 2: paint an underlay that matches the iframe's internal background
iOS Safari renders rounded-corner iframes with a subtle alpha blend at each corner. A few pixels of partial transparency where the curve sits. Whatever is behind the iframe shows through that blend. If the iframe's internal background is #1f1f1f (Google's reCAPTCHA dark theme) and your page background is closer to #0a0a0a, those few corner pixels read as lighter than the iframe and darker than the page. From a normal viewing distance, that reads as a halo.
The fix is to paint the wrapper with the same color the iframe paints itself, so the corner blend is invisible.
.contact__captcha .g-recaptcha {
width: fit-content;
max-width: 100%;
border-radius: 3px;
overflow: hidden;
}
[data-theme="dark"] .contact__captcha .g-recaptcha {
background: #1f1f1f;
}
[data-theme="light"] .contact__captcha .g-recaptcha {
background: #f9f9f9;
}
width: fit-content keeps the wrapper from extending past the iframe horizontally. Otherwise you would see the wrapper's own background sticking out the right side of the widget. border-radius: 3px matches the iframe's corner radius so the underlay corners line up with the iframe's. overflow: hidden is belt-and-suspenders.
After this, the corners no longer halo. They blend cleanly into the wrapper, which blends cleanly into the page. You would ship this and call it done.
Layer 3: clip 2 pixels off the iframe itself
Except on iOS Safari, there was still a 1 to 2 pixel bright edge running the perimeter. This one was inside the iframe. Google's dark-theme reCAPTCHA paints a subtle 1px highlight on the inside of its own border, presumably to mimic the elevation it had with the wrapper box-shadow we just removed. I cannot disable it. It is their CSS, in their iframe, on their domain.
What I can do is clip the iframe.
.contact__captcha iframe {
clip-path: inset(2px);
}
clip-path: inset(2px) removes 2 pixels from each edge of the iframe before it composites. The bright pixels are in those 2 pixels. They are gone now.
The trade is that I am cropping 2 pixels of widget. I sat with this and looked at what is actually in those edges. Google gives the checkbox, the label text, the logo, and the Privacy and Terms links plenty of internal padding. They all sit well inside a 2px margin. The click target is unaffected. The widget still works exactly the same, just with two pixels less air around it.
I would rather lose two pixels of margin than ship a glowing rectangle.
Why it had to be three things
Each layer fixes a different cause. Skip layer 1 and you still see the wrapper's box-shadow. Skip layer 2 and the iframe's rounded corners blend into the page background. Skip layer 3 and the iframe's own internal highlight bleeds through.
If you copy only the clip-path line, you will get a different version of the same bug at a different layer. The fix is the stack.
Was it worth it
A bright edge on a reCAPTCHA widget will not lose me a client. Probably nobody but me has ever noticed it. But the contact form is the one place on the site where someone is asking to give me money, and I want it to look like the rest of the site looks: composed, dark, intentional.
An hour of CSS to make the form not embarrass itself on the device most people will see it on first. That math worked out for me.
The drop-in version
If you are using aryehraber/statamic-captcha (or any reCAPTCHA v2 widget) inside a true dark mode and you have noticed the same iOS Safari halo, here is the full stack. Tweak class names to match your markup.
/* Layer 1: nuke the decorative chrome on the wrapper Google injects */
.contact__captcha .g-recaptcha,
.contact__captcha .g-recaptcha *:not(iframe) {
border: 0 !important;
box-shadow: none !important;
outline: none !important;
}
/* Layer 2: paint an underlay that matches reCAPTCHA's internal bg
so iOS Safari's rounded-corner blend has nothing to show through */
.contact__captcha .g-recaptcha {
width: fit-content;
max-width: 100%;
border-radius: 3px;
overflow: hidden;
}
[data-theme="dark"] .contact__captcha .g-recaptcha {
background: #1f1f1f;
}
[data-theme="light"] .contact__captcha .g-recaptcha {
background: #f9f9f9;
}
/* Layer 3: clip 2px off the iframe to remove the bright edge
Google paints inside its own (cross-origin, untouchable) iframe */
.contact__captcha iframe {
clip-path: inset(2px);
}
That is it. The bug is gone. The widget still works.