Repository navigation
Contact modals on one native-dialog component, with consent-aware tracking - #773
Merged
Merged
Conversation
…cking - Add `@util/form-guard`: field validation with inline hints, XSS pre-check, honeypot, timing check and consent-aware tracking fields, shared by ContactForm, the dev-services form and the contact modals. - Add `Modal.astro` (native <dialog> shell) and `ContactModal.astro` on top of it; dev-services, Trendz and smart-farming now use it. The old modals rendered inside the isolated main pane, so the header and the chat launcher painted over them; the smart-farming one also sent no tracking fields and validated with alert(). - Contact modals redirect to /contact-us-thanks/ and send client_id and fpr; the dev-services form uses FORMSPREE_CONTACT and validates once per submit. - Add `@util/modal-dialog` (scroll lock, backdrop close, unlock after the exit fade) and move the cookie preferences, Cloud region, IoT Hub install and blog image lightbox dialogs onto it. The lightbox is now a <dialog> that opens from the keyboard. - Search overlay: role=dialog, aria-modal, a label and the shared scroll lock. - IoT Hub install dialog gets an accessible name; fix the stale z-index comment in PromoBanner; remove the unused close-icon.svg.
vvlladd28
commented
Oct 16, 2026
vvlladd28
left a comment
Member
Author
There was a problem hiding this comment.
Review summary
Reviewed 20 changed files in Contact modals on one native-dialog component, with consent-aware tracking. Left 1 comment inline.
The one finding is in the shared backdrop-close check in src/util/modal-dialog.ts: it holds for the cookie, Cloud region and IoT Hub dialogs, whose inner panel fills the dialog box, but not for Modal.astro, where the <dialog> is the padded card itself. Clicks on the padding, or a text selection dragged out of a field, close the contact modal and drop what the visitor typed.
Not reviewed: public/images/close-icon.svg (deleted asset) — no reviewable logic.
This review was auto-generated. Findings may contain errors — please verify before applying changes.
The <dialog> was the padded card itself, so a click on its padding, or a text selection dragged out of a field, landed on the dialog and closed the contact modal. The card moves into an inner panel inside a transparent, viewport-filling dialog, and the backdrop close also requires the press to start on the backdrop.
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.
What
Shared form protection —
@util/form-guardField validation with inline hints, the XSS pre-check, the honeypot, the render-to-submit timing check and consent-aware tracking fields, in one module.
ContactForm, the dev-services form and the contact modals use it instead of their own copies.One contact modal —
Modal.astro+ContactModal.astroModalis a native<dialog>shell: title, close button, backdrop, the form-modal card, anintroslot.ContactModalputs the contact form in it. It replaces three separate modals:/services/development-services/.dev-modaldiv,<img>close button not reachable by keyboard, no Esc, noaria-modal/nameContactModal/products/trendz/TrendzContactModal(deleted), hand-rolled focus trap, honeypot armed but never checkedContactModal/smart-farming-demo/.main-pane),alert()validation, no UTM/client_id/fpr/honeypotContactModal, intro text via the slot,subjectsent as a hidden fieldAll of them now:
FORMSPREE_CONTACT;/contact-us-thanks/(asContactFormdoes);utm_*/fpronly with Marketing consent andclient_idonly with Analytics consent, and refill on consent change.Triggers use
data-contact-modal-open(bare = the page's only contact modal).ServicesBanner'sbuttonOnClickprop becomesbuttonOpensContactModal.The dev-services in-page form gets
client_id/fpr,FORMSPREE_CONTACT, and no longer validates twice per submit (inlineonsubmit+ listener).Shared dialog wiring —
@util/modal-dialogbindModalDialog()handles the scroll lock, backdrop close, and unlocking after the exit fade. These now use it:<dialog>. Images open it from the keyboard too (Enter / Space), so each image in a post is now a Tab stop. Esc no longer releases another overlay's scroll lock.Smaller fixes
role="dialog",aria-modal,aria-label, sharedlockScroll. It stays a<div>, since Google CSE's suggestion dropdown is appended to<body>and would render under a top-layer dialog.aria-labelledby.PromoBanner.astro: z-index comment had the cookie banner at 9999; it is 1100.public/images/close-icon.svg.Visual changes
trendz-*, otherwise the same layout.Behaviour notes
gtm_form/gtm_button, as the dev-services one already did.?subject=; it always sendsCustom Development.Checked
Manually in Chrome on the dev server (no form was submitted; validation and bot paths were exercised with synthetic
submitevents):pnpm lint:eslintpasses on the touched files. Not run yet:pnpm check,pnpm build,pnpm lint:linkcheck.Follow-ups
Services/ContactModal.astrocan be replaced by<ContactModal id="poc-contact" title="Let's talk about your project" formId="poc_sales_modal" tags="sales" />; its baredata-contact-modal-opentriggers work unchanged. Until then it still references the removedclose-icon.svg./pricing/calculator modals still useportalToBody+ their own controller.