fix: render URI buttons as anchors to fix mobile popup blocker#489
Merged
Conversation
Primary/secondary buttons whose step exposes a URI used to navigate via window.open() after awaiting step.complete. iOS Safari and Chrome Android only honor window.open(_, '_blank') when it runs synchronously inside the user gesture — any await breaks that gesture chain and the new tab is silently dropped, so users see the flow complete but no tab opens. The buttons now render as <a href target rel="noopener noreferrer"> so the browser handles navigation natively, which is never popup-blocked. The button's onClick still fires step.complete for analytics/state. When the consumer overrides the navigate prop (e.g. next/router.push), the button keeps rendering as <button> so the custom handler still runs exactly as before. Visual styling is unchanged — computed text styles (font, size, weight, line height) are pixel-identical and the base button styles now explicitly set cursor:pointer and text-decoration:none so the anchor matches the button in every browser. Storybook stories added under Components/Announcement: PrimaryButtonAsLink (renders as <a>) and PrimaryButtonLinkWithCustomNavigate (renders as <button>). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ousel
The footer CSS used `& > button { flex-basis: 50%; flex-grow: 1 }` to make
primary and secondary share the row evenly. When the primary renders as
an anchor (the link-button path) the selector misses it and only the
button-shaped secondary gets the 50/50 rule, so the anchor shrinks to its
content width and the button stretches to fill the rest.
Match `& > button, & > a` so both element types size identically.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
useStepHandlers.handlePrimarycallsnavigate(uri, target)(defaultwindow.open) afterawait step.complete(...). iOS Safari and Chrome Android only honorwindow.open(_, "_blank")while the user gesture is active — anyawaitbreaks the gesture chain and the new tab is silently blocked. Desktop browsers are lax about this, which is why it works there.navigate, render the button as<a href target rel="noopener noreferrer">instead of<button>. Native anchor clicks are never popup-blocked.onClickstill firesstep.completefor analytics/state. Buttons with no URI and buttons under a customnavigateprop are unchanged.What changes
useStepHandlersreturns newprimaryButtonProps/secondaryButtonPropsobjects that include{ as: 'a', href, target, rel }when appropriate. Call sites (Announcement,Banner,Card.FlowCard,Tour.TourStep,Checklist.Collapsible,Checklist.Carousel,Checklist.Floating) spread these in place ofonClick={handlePrimary}.FrigadeContextgainshasCustomNavigate: booleanso the hook can detect when a customnavigatewas passed and fall back to button rendering.Button.styles.tsaddscursor: pointerandtext-decoration: noneto the base styles so an<a>matches a<button>in every browser. Computed font/size/weight/line-height are already inherited from the innerText.Body2so were already identical.Test plan
tsc --noEmitcleanPrimaryButtonAsLink(renders<a>with default navigate) andPrimaryButtonLinkWithCustomNavigate(renders<button>when consumer overridesnavigate). Mock the flow via__readOnly+__flowStateOverrides.<a href="..." target="_blank" rel="noopener noreferrer"><button><button>🤖 Generated with Claude Code