Form 03
A contact form that opens in a dialog over the page.
Documentation
-
Copy to Webflow
Add Geist and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: Webflow removes any font your site has not installed. -
Add custom CSS
Custom CSS in WebflowThis CSS already ships inside the Webflow embed, so a pasted section needs nothing added. Paste it yourself, in the page or the site custom code, only if the effect runs on several pages. The tutorial explains where.
See the tutorialThe look of the preview is theform-03-*andform-set-*classes of the paste: the page behind, the sheet, the fields at rest and the buttons. The width of the sheet is the max-width ofform-03-panel, which it loses on the Mobile portrait breakpoint to take the whole width. The Embed holds what moves: the slide of the sheet, under[data-form-set-dialog="sheet"](take the valuesheetoff the layer and the panel rises in the middle instead), and the veil over the page, thebackground-colorand thebackdrop-filterof[data-form-set-dialog][data-form-set-state="open"]. The states read their colours from five variables,--form-set-ink,--form-set-ground,--form-set-rule,--form-set-accentand--form-set-error, which the first Embed turns over for the sheet, cream on ink, in the rule on[data-form-set-dialog="sheet"]: for a sheet of another colour change them there, with the colours of the classes. The three buttons are Link blocks, because Webflow has no button that holds layers; the script makes them open, close and send. Do not hide the layer withdisplay: noneor an interaction: it would never show. -
Add custom JS
Custom JavaScript in WebflowThis script already ships inside the Webflow embed, settings block included. You paste it yourself, before the closing body tag of the page or the site custom code, only when the effect has to run on several pages, and the tutorial says how to choose.
See the tutorial -
Use it on your own elements
How to add a custom attribute in WebflowA custom attribute is a pair, a name and a value, typed in the Settings panel of the element you selected, under Custom attributes. The name is what the script looks for, the value is what it reads. The tutorial shows the panel.
See the tutorialCustom attributesSelect the element on the canvas and open the Settings panel. Under Custom attributes, add a pair: the name on the left, the value on the right.Custom attribute What it does data-form-set Marks the form. It takes no value: leave the value field empty. In the paste it is on the Form inside the Form block, in the panel of the dialog. The settings below go on the same element. data-form-set-dialog="sheet" On the layer that covers the page and holds the sheet, the sheet holding the form. Its value sheet makes the panel slide in from the right; with no value the panel rises in the middle of the window instead. The stylesheet lays the layer over the window and hides it until it is opened; on the Webflow canvas, which runs no Embed, it sits in the flow of the section, where it is edited. [ Required ] data-form-set-open On what opens the dialog: any button or link, anywhere on the page, as many as you like. It takes no value, or the id of the dialog when the page has several: data-form-set-open="quote". [ Required ] data-form-set-close="cross" On what closes the dialog, inside it. It takes no value, or cross to draw a cross in place of its word, which stays its name for a screen reader. Escape and a click outside the panel close it too, and so does the form, the time of the setting closeAfter after it was sent. [ Optional ] data-form-set-field On the box of one field: one label and one control (a text, email or phone input, a select, a textarea), with its hint when it has one. It takes no value. The engine writes the state on it and adds what it draws inside it. A control outside such a box is sent with the form but has no state and is not checked. [ Required ] data-form-set-submit On the send button: a submit button in plain HTML, a Link block in Webflow, which has no button that can hold layers. It takes no value. Without it the form is still checked when it is sent, by Enter for one, but nothing shows it leaving. [ Required ] data-form-set-hint On the line of help under a control. It takes no value. The reason takes its place while the field is wrong, then the hint comes back. A field without one gets a line from the engine, dressed like the first hint of the form. [ Optional ] data-form-set-required="Your name, please." On one field: its own words for a reason, in place of the form's. The same goes for email, tel, short, pattern and check. [ Optional ] data-form-set-select="native" On one field: leaves its select to the list of the system, in place of the list the engine draws. On the form it is the setting select, for every select at once. A touch screen gets the system's picker either way, and the native select is what the form sends in both cases. [ Optional ] data-form-set-state Written by the engine. On a field: up, focus, filled, valid, error, disabled, several at once. On the send button: loading, done or failed. On the form: done. On a dialog: open. A field whose list is open also carries data-form-set-list="open". Never written by hand; the stylesheet reads them, and so can a rule of yours. [ Optional ] SettingsEvery setting is a custom attribute too, added the same way and on the same element. An attribute always wins over the defaults written in the code.Attribute Default What it is data-form-set-send auto How the form leaves once every field is right. Options: auto lets a Webflow Form block send it to Webflow, and anywhere else posts the form to its action the usual way, the page changing; fetch posts the values to the action without leaving the page and reads the status of the answer. data-form-set-sending Sending The word on the button while the form is on its way. Options: any text, in the language of the page. data-form-set-sent Sent The word on the button once the form has left, beside the tick. Options: any text. data-form-set-failed Try again The word on the button when the send failed; the button can be pressed again. Options: any text. data-form-set-required This field is required. The reason under a required field left empty. Options: any text; data-form-set-required on one field gives it its own, "Your name, please.". data-form-set-email Enter a valid email address. The reason under a field of type email that does not hold an address. Options: any text. data-form-set-tel Enter a valid phone number. The reason under a field of type tel that does not hold a phone number, six digits at least. Options: any text. data-form-set-short A little more, please. The reason under a text shorter than the minlength of its control. Options: any text. data-form-set-pattern Check the format. The reason under a text the pattern of its control refuses. Options: any text; say the format expected, "Two letters, then four digits.". data-form-set-check Please tick this box. The reason under a required checkbox left empty. Options: any text. data-form-set-select drawn The list a select opens. Options: drawn, a list the engine draws under the control in the colours of the form, the native select staying underneath; native, the list of the system, which a touch screen gets either way. data-form-set-select="native" on one field leaves that select alone. data-form-set-close-after 2400 How long a dialog stays on the answer once its form is sent, in milliseconds. Only read by a form in a dialog. The way the form is sentNothing to connect: the paste is a native Webflow Form block, and Webflow receives what it sends. The entries are listed in Site settings, Forms, each field under the name of its label, and the address told of a new entry is set in the same place, under Form notifications. Entries only arrive from a published site, its webflow.io address included: the Designer and its preview send nothing. Once Webflow has the entry it hides the form and shows its Success message; when it could not, it shows its Error message and the button says Try again. Both are Webflow's own blocks, under the Form in the Navigator: select the Form block, open its settings and choose the state to see, restyle and rewrite each one. To add a field, duplicate a field block and give the new field its own name in its settings; to remove one, delete its block; Required is a switch in the same settings. Leave the Action of the form empty, or Webflow receives nothing. Only when the form is not a Webflow Form block do the attributes below apply. In the dialog, Webflow's Success message takes the place of the form, the dialog closes a moment later, and opened again it shows that message until the page is loaded again.Custom attribute What it does action="https://..." On the form: the address the values are posted to, an endpoint of your own or one of a form service such as Formspree or Basin. Not a custom attribute in Webflow, where it is the Action field of the Form settings, to leave empty for Webflow to receive the entries. data-form-set-send="fetch" On the form: posts the values to the action without leaving the page, as FormData, with the header Accept: application/json. An answer in the 200s shows Sent, anything else Try again. It is the setting send, so it may also be set for the whole site. method="post" On the form: how the values are sent, POST when nothing is written. With GET and fetch they travel in the address.
Two files, no build step.
It runs in the browser only. Load it with a script tag in plain HTML, in a
client-side script in Astro, never in the frontmatter. In React, call
FormSet.mount(ref.current) in useEffect and
destroy() in its cleanup.
-
Tune it, either way
Per element with an attribute, or once for the whole site by editingDEFAULTSat the top of the file.Attribute Default What it is Lower Higher data-form-set-send auto How the form leaves once every field is right. Options: auto lets a Webflow Form block send it to Webflow, and anywhere else posts the form to its action the usual way, the page changing; fetch posts the values to the action without leaving the page and reads the status of the answer. data-form-set-sending Sending The word on the button while the form is on its way. Options: any text, in the language of the page. data-form-set-sent Sent The word on the button once the form has left, beside the tick. Options: any text. data-form-set-failed Try again The word on the button when the send failed; the button can be pressed again. Options: any text. data-form-set-required This field is required. The reason under a required field left empty. Options: any text; data-form-set-required on one field gives it its own, "Your name, please.". data-form-set-email Enter a valid email address. The reason under a field of type email that does not hold an address. Options: any text. data-form-set-tel Enter a valid phone number. The reason under a field of type tel that does not hold a phone number, six digits at least. Options: any text. data-form-set-short A little more, please. The reason under a text shorter than the minlength of its control. Options: any text. data-form-set-pattern Check the format. The reason under a text the pattern of its control refuses. Options: any text; say the format expected, "Two letters, then four digits.". data-form-set-check Please tick this box. The reason under a required checkbox left empty. Options: any text. data-form-set-select drawn The list a select opens. Options: drawn, a list the engine draws under the control in the colours of the form, the native select staying underneath; native, the list of the system, which a touch screen gets either way. data-form-set-select="native" on one field leaves that select alone. data-form-set-close-after 2400 How long a dialog stays on the answer once its form is sent, in milliseconds. Only read by a form in a dialog. Closes sooner; 0 closes it at once Leaves more time to read the word on the button; off keeps it open until the visitor closes it The way the form is sentOnce every field is right the form leaves in one of four ways. With anactionand nothing else, the browser posts it and the page changes. Withdata-form-set-send="fetch"it is posted to that action without leaving the page, and the button says Sent or Try again from the status of the answer. With a listener onformset:submitthat callspreventDefault(), the page sends the values itself and ends withdetail.done(true)ordetail.done(false). Inside a Webflow Form block, Webflow sends it. Theaction="#"of the markup is a placeholder and sends nothing.Custom attribute What it does action="https://..." On the form: the address the values are posted to, an endpoint of your own or one of a form service such as Formspree or Basin. Not a custom attribute in Webflow, where it is the Action field of the Form settings, to leave empty for Webflow to receive the entries. data-form-set-send="fetch" On the form: posts the values to the action without leaving the page, as FormData, with the header Accept: application/json. An answer in the 200s shows Sent, anything else Try again. It is the setting send, so it may also be set for the whole site. method="post" On the form: how the values are sent, POST when nothing is written. With GET and fetch they travel in the address. -
Drive it from JavaScript
When the page opens or closes the dialog itself, sends the values itself, fills or checks the form from a script, or reacts to the form being sent.const element = document.querySelector('[data-form-set]'); element.addEventListener('formset:sent', (e) => { console.log(e.detail); }); const set = FormSet.mount(element); // the form auto() mounted, in its dialog set.open(); // opens the dialog the form sits in set.close(); // closes it // Send the values yourself: the button waits until done() is called, // and the dialog closes by itself once it is. element.addEventListener('formset:submit', async (event) => { event.preventDefault(); const answer = await fetch('/api/contact', { method: 'POST', body: new FormData(event.detail.form) }); event.detail.done(answer.ok); // true shows Sent, false shows Try again }); set.set('email', 'pam@dundermifflin.com'); // as if it had been typed set.validate(); // shows the reasons, returns true when the form can leave set.reset(); // empties it and takes every state off // When the form leaves the page: FormSet.destroy(element); // removes what the engine added to the form and to its dialog, gives the markup back as it was
-
Fit it to your page
The dialog
The dialog is a layer marked
data-form-set-dialog="sheet"that holds one box, the sheet, and the sheet holds a small label and the cross, the heading, a line of text and the form. The stylesheet lays the layer over the whole window and hides it; opened, the layer darkens and blurs the page over one second while the sheet slides in from the right, then the label, the heading, the line and the direct children of the form come in one after the other. Closing, the sheet slides back out in 0.7 seconds.- **The value
sheetis what makes it slide.** With no value,data-form-set-dialogalone, the same panel rises into place instead, and comes up from the bottom edge on a phone: then centre it in the layer,align-items: centerandjustify-content: centeronform-03-overlay, in place of the right edge. The timings of the sheet are in the mechanics of the stylesheet, underdata-form-set-dialog="sheet". - To open it from any button or link of the page, add
data-form-set-opento it, with no value. It can be anywhere: in the navigation, in a footer, in another section, as many of them as you like. In Webflow: select the button or the link, open the Settings panel (the gear) and add the custom attributedata-form-set-open, value left empty. A link keeps itshreffor a page without JavaScript; the click is kept on the page. - Several dialogs on a page: give each layer an
id, in Webflow the ID field of its Settings panel, and write that id as the value of what opens it,data-form-set-open="quote". An opener with no value opens the first dialog of the page. One dialog is open at a time; the fields of two forms need ids of their own, as on any page. - What closes it: anything inside it marked
data-form-set-close, the cross of the preview, whose word Close stays its name for a screen reader; the Escape key; a click on the layer, outside the sheet; and the form itself,closeAftermilliseconds after it was sent, 2.4 seconds unless you change it.data-form-set-close-after="off"on the form keeps the dialog open on the answer. - While it is open the page behind does not scroll, Tab and Shift Tab stay inside the panel, and the focus is put on the first field. When it closes the focus goes back to what opened it.
- Opened again after a send, the form starts empty. A native Webflow form does not: Webflow has replaced it by its Success message, which is what the dialog shows until the page is loaded again.
- On a phone, under 480 pixels, the sheet takes the whole width and still comes from the right. The sheet is as tall as the window at least; the layer scrolls when it is taller, and ends where the keyboard starts, so the send button can always be reached.
- For a screen reader the layer carries
role="dialog",aria-modal="true"andaria-labelledbypointing at theidof its heading. Keep the three when you rewrite the markup; rename the heading freely, its id is what counts. - From a script,
FormSet.mount(form).open()and.close().
The dialog in Webflow
The paste is one section: the page behind, then the dialog as its last block, then the code.
- On the canvas the dialog is not over the page. What lays it over the window and hides it is in the Embed, which the Designer does not run, so the dialog sits in the flow, under the foot line, as an ordinary block: the layer (
form-03-overlay), the sheet of ink against its right edge (form-03-panel), the label, the Close link, which shows its word there and its cross on the site, the heading, the line and the Form block, cream on ink. Select, restyle and rewrite each of them there, like anything else. In preview and on the published site it is hidden until a click opens it. - The sheet is edited as a box: its width is the
max-widthofform-03-panel, 540 pixels, and its padding, colour and shadow are on the same class. The veil over the page and the cream of the states are in the Embed: see The colours. - The two buttons are Link blocks, as wide and as tall as the buttons of the preview: each class that has a size sets
box-sizing: border-box, markeddata-form-set-openanddata-form-set-close. To open the dialog from a button of your own elsewhere on the page, add the attribute to it and delete the one of the paste, or keep both. - To use the dialog on several pages, turn the layer into a Webflow Component and place it on each page, or put it in the footer Component; the Embeds go with it, or once in the site's custom code.
- The phone layout is on the Mobile portrait breakpoint: the sheet loses its
max-widthand takes the whole width. - The rest is Form 01's: a native Form block, Webflow's two messages, a Link block marked
data-form-set-submitfor the send button.
Sending the form
Once every field is right the form leaves, in one of four ways. The button shows
Sendingand a running bar meanwhile, thenSentwith a tick, orTry again.- In Webflow, as a native form. The paste is a real Webflow Form block: the Designer shows Form block, Form, the fields with their own settings, and the two message blocks. Nothing to connect. Webflow receives the entries itself: they are listed in Site settings, Forms, and on the site's Forms page, each field under the name of its label. The address that is told of a new entry, and the subject of that mail, are set in the same place, Site settings, Forms, Form notifications. Entries are only received on a published site, the webflow.io address included; the Designer and its preview send nothing. When Webflow has received the form it hides it and shows its Success message; when it could not, it shows its Error message under the form and the button says
Try again. Both messages are Webflow's own blocks, in the Navigator under the Form: select the Form block, open its settings and choose the state to see and restyle each one, and rewrite its words on the canvas. The engine knows a native form by its Form block, and reads which message Webflow shows. Do not give that form an Action in its settings: a Webflow form with an action is posted there instead, and Webflow receives nothing. - To an endpoint, without leaving the page. Give the form the address in its
actionand adddata-form-set-send="fetch". The values are posted withfetch, asFormData, with the form'smethod(POST when none is written) and the headerAccept: application/json, which is what Formspree, Basin, Getform and the like expect. An answer in the 200s showsSent; any other, or no answer, showsTry again. The endpoint has to accept requests from the page's origin. - The usual way. With an
actionand nothing else the browser posts the form and goes to the page the server answers with, as any form does. The states of the fields and the check before sending still play. - By hand. The form fires
formset:submitbefore it leaves, with{ form, done, reset }indetail. CallpreventDefault()on it, send the values yourself, thendetail.done(true)ordetail.done(false);detail.reset()empties the form to start again. This is how the preview works, and all its page adds to the two files: a listener ondocumentcallspreventDefault(), thendetail.done(true)after 1800 ms anddetail.reset()after 5200 ms. It sends nothing, holds the button, shows it sent, lets the dialog close, then empties the form. A page that must behave exactly as the preview carries that listener; a real page sends instead.
html <script> /* The preview sends nothing: it holds the form, shows it as sent, then empties it. */ document.addEventListener('formset:submit', function (event) { event.preventDefault(); setTimeout(function () { event.detail.done(true); }, 1800); setTimeout(function () { event.detail.reset(); }, 5200); }); </script>The snippet is written with
action="#", a placeholder that sends nothing: replace it with your address, or use one of the other ways. The Webflow paste leaves it out.Fields
- A field is one box marked
data-form-set-fieldthat holds onelabeland one control: aninputof type text, email, tel, number, url or password, aselect, or atextarea. The label points at the control withfor. - To add a field, copy the box of a field of the same kind and give the new control its own
id, the same value in theforof its label, and its ownname: the name is what the entry is filed under. In Webflow, duplicate the field block in the Navigator and rename the field in its settings. - To remove a field, delete its box. To reorder, move the boxes: the engine reads them in the order of the page, and nothing else depends on it.
- What makes a field wrong is what its control says:
required,type="email",type="tel",minlength,pattern. Add or take off the attribute to change the rule. In Webflow, Required is a switch in the field's settings, andminlengthandpatternare custom attributes on the field. A field is checked when the visitor leaves it, then again at every key while it is wrong, and every field is checked when the form is sent: the first wrong one takes the focus. - A line of help goes in an element marked
data-form-set-hintinside the box. The reason takes its place while the field is wrong. - The preview has three fields, a name, an email and a message, one a row, and the engine takes every kind of the set. A select added here wears the class of the inputs and needs nothing more. A checkbox, a
labelmarkeddata-form-set-field="check"that holds theinputand its sentence in aspan, has its states in this stylesheet but not its look at rest: copy the three rulesform-set-check,form-set-boxandform-set-check-textfrom the look ofform-01.css, or draw the box with a class of your own. - A select opens a list of its own, see below. It shows its first option while nothing is chosen: leave that option's value empty, and its text too, since the label is the invitation.
- The textarea grows with its text. With a
maxlengthit shows a count, which darkens over the last tenth.
The list of a select
A select opens a list drawn by the engine in place of the system's: a panel under the control, one line per option, the chosen one ticked.
- **The native
selectstays in the page**, under the list, and is what the form sends: itsname, itsvalue, itsrequired. The list is only a picture of its options, hidden from screen readers, which read the select itself as they always do. Add, remove or reorder theoptionelements and the list follows at the next mount; an option with an empty value, the invitation, and a disabled one are left out of it. - With the mouse, a press on the control opens the list, a click on a line chooses it, a click anywhere else closes it.
- With the keyboard, Enter, Space or an arrow opens it; the arrows, Home and End move the highlighted line and the value moves with it; Enter, Space or Tab keep the value, Escape puts back the one it had.
- On a touch screen (
pointer: coarse) the list is not drawn: the system's own picker opens, which is the better one under a thumb. - Its colours are the form's variables: the panel is
--form-set-ink, its words--form-set-ground, the tick of the chosen line--form-set-accent. For more, the list is.form-set-listand a line.form-set-option, with[data-form-set-active]on the highlighted line and[data-form-set-selected]on the chosen one: restyle them in a rule of your own, after the stylesheet. A list longer than its height scrolls. - To keep the system's list everywhere, set
selecttonative:data-form-set-select="native"on the form, or on the box of one field for that select alone. It is the choice for a very long list, a country for one, where the system's search by typing is worth more than the look. - While the list is open the field carries
data-form-set-list="open".
The words of the errors
Each reason is a setting, so an attribute on the form:
data-form-set-required="Ce champ est obligatoire.", and the same foremail,tel,short,patternandcheck. The same attribute on one field's box changes the words for that field alone. The words of the button aresending,sentandfailed. For every form of the site at once, change the settings block at the top of the Embed, or declarewindow.FormSetSettings = { required: '...' }before the script.The colours
Five custom properties, set at zero specificity on the form. Give them your own values on the form or on anything above it, the section or
body:--form-set-ink: the text of the fields, the line of the focus, the tick.--form-set-ground: the ground the form sits on; the tick of the checkbox is drawn in it.--form-set-rule: the rule under a field at rest.--form-set-accent: the plate and the bar of the button.--form-set-error: a wrong field and its reason.
The sheet turns them over, cream on ink. In the mechanics of this stylesheet, in Webflow the first Embed, the rule on
[data-form-set-dialog="sheet"]and on the form inside it sets--form-set-inkto the cream,--form-set-groundto the ink,--form-set-ruleto a quarter of cream and--form-set-errorto a lighter red, so the line of the focus, the ticks, the cross and the reasons are right on the dark sheet. For a sheet of another colour, change those four values there, or set them again in a rule of your own on the same selector, and change the colours of the classes: the ground ofform-03-panel, the text ofform-set-input, the grey ofform-set-label,form-set-hint,form-03-tagandform-03-lead, the pillform-03-send.The veil of the dialog is not one of them: it is the
background-colorand thebackdrop-filterof[data-form-set-dialog][data-form-set-state="open"], in the mechanics of the stylesheet, in Webflow the first Embed. Change the two values there, or in a rule of your own written after it.The look of the preview, the last part of the stylesheet and the classes of the Webflow paste, names its own colours: change them there too, in the Style panel or in that part of the file.
From JavaScript
FormSet.mount(form, overrides)returns the instance of a form, mounting it if it was not:{ element, settings, fields(), set(name, value), enter(name), leave(name), validate(), submit(), done(ok), reset(), refresh(), list(name, shown), open(), close(), destroy() }.setwrites a value as if it had been typed;validateshows the reasons and returns whether the form can leave;listshows the drawn list of a select, or closes it withfalse;openandcloseare for the dialog the form sits in, and do nothing for a form on the page.FormSet.auto(root)mounts every marked form insideroot, and runs once on its own;FormSet.destroy(form)gives the markup back, the dialog's too once its last form is gone.- Events, all bubbling from the form:
formset:ready,formset:invalid(with the wrong controls indetail.fields),formset:submit(cancelable, see above),formset:sentandformset:failed. - The state is on each element as
data-form-set-state:up,focus,filled,valid,error,disabledon a field, several at once;loading,doneorfailedon the button;doneon the form;openon a dialog. - **Under
prefers-reduced-motion: reduce** every state still shows, at once: the transitions and the bar of the button are cut to nothing. - Without JavaScript the form is a plain form: labels above their fields, the browser's own checks, a button that posts it.
- The words of the demo are the demo's: on a real page every label is the user's own, in the page's language, or asked for.
- **The value
-
Avoid the pitfalls
- **
action="#"sends nothing.** It is the placeholder of the snippet. A form outside a Webflow Form block needs a real address,fetchor not, or a listener onformset:submit. - In Webflow, entries only arrive from a published site. The Designer preview plays the states when custom code is enabled, and sends nothing.
- In Webflow the send button is a Link block, marked
data-form-set-submit. A native Webflow Submit button is aninput, which cannot hold the plate, the label and the bar. The script makes the Link send the form, and Enter in a field does too. A native Submit button marked the same way still sends, with no animation on it. - One look of fields per page. The forms of the set share the engine, and each stylesheet draws the fields its own way: Form 03 writes them in cream for its sheet of ink, so Form 01 or Form 02 on the same page would mix their looks with it. Two forms of the same number on a page are fine.
- The opener and the dialog are found by attribute, not by place. A button marked
data-form-set-openwith no dialog on the page does nothing, and a dialog nothing opens stays hidden. In Webflow the Embed says so in the console; the two files alone say nothing. - **Do not hide the dialog with a Webflow interaction or
display: none.** The stylesheet hides it withvisibility, which is what lets the panel move in and out. A layer set todisplay: nonein the Style panel never shows. - The paste carries two Embeds. The first holds the settings and what moves, the second the engine. Keep both, in that order.
- **Every control needs a
name**, or its value is not sent, and alabel. - **A control outside a box marked
data-form-set-field** is sent with the form but is not checked and has no state. - Nothing above the dialog may move. The layer is
position: fixed: inside an element with atransform, afilteror aperspectiveit is fixed to that element instead of the window. Keep it out of such a box, at the end of the section or of the page. - The browser's own bubbles are off: the engine sets
novalidateon the form while it is mounted, and gives it back on destroy. - The stylesheet draws the states. Without
form-03.cssthe fields are checked but nothing shows, and the engine says so in the console. - **With
fetch, a server that redirects to a thank-you page** answers that page instead of a status: use the endpoint's JSON mode, which theAcceptheader asks for. - The label floats over the control, so the control keeps no placeholder of its own: a placeholder would show under the label at rest.
- **
One click on Copy the AI prompt copies everything an assistant needs to build this effect: the complete code, the markup, the dependency, every setting and every pitfall.
What it does
Form 03 is an animated form for Webflow and vanilla JavaScript, with no library. Also called slide in contact form, modal contact form or side panel form.
A contact form that opens in a dialog over the page. Any button or link marked for it opens the dialog: the page behind darkens and blurs while a sheet of ink, the whole height of the window, slides in from the right, slow at first, then settling; its label, its heading and the fields of the form come in one after the other once it is nearly there. On a phone the sheet takes the whole width. Escape, a click outside the sheet or the cross close it, Tab stays inside it, the page behind does not scroll, and the focus goes back to the button that opened it. Inside, the fields are the underline fields of the Form set, cream on ink, one a row: the label rests in the line of its field and floats up on focus, a line is drawn under the field, a tick draws itself when the field is right, a wrong field turns red and says why under it. The fields are checked as the visitor leaves them, with what the controls already say: required, an email, a minimum length. The send button shakes its head once when a field is still wrong, runs a bar while the form leaves, then says Sent with a tick, and the dialog closes by itself a moment later. It is a real form: pasted in Webflow it is a native Form block whose entries land in the site's Forms tab, and the dialog sits on the canvas where it can be edited; elsewhere the form posts to its action, with fetch or the usual way, or hands the values to the page. Several dialogs can share a page. Reduced motion respected. No library. Form 03 of the Form set. Pattern type modal form, popup contact form, dialog.
Updated
Questions
Paste the section: it holds a layer marked data-form-set-dialog="sheet" with a sheet and a native Webflow Form block inside, and a Link block marked data-form-set-open. On the published site the layer is hidden until that link is clicked, then the sheet slides in from the right over the darkened page. Nothing to wire, and no Webflow interaction to build.
Yes. Add the custom attribute data-form-set-open, with an empty value, to any button or link, in the navigation or the footer for one, as many as you like. With several dialogs on a page, give each layer an ID and write it as the value, data-form-set-open="quote".
It is not hidden on the canvas. What lays the dialog over the window, hides it and slides it in lives in the Embed, which the Designer does not run, so the layer, the sheet, the heading and the Form block sit in the flow of the section, where each one is selected, restyled and rewritten like any block.
The cross, the Escape key, a click outside the sheet, and the form itself 2.4 seconds after it was sent, a time set by data-form-set-close-after (or off). While it is open Tab stays inside the sheet, the page behind does not scroll, and the focus returns to the button that opened it. The layer carries role="dialog" and aria-modal, and the cross keeps the word Close as its name.
Yes. The form inside the dialog is a real Form block with no action, so Webflow receives it and lists the entries in Site settings, Forms. Outside Webflow the form posts to its action, with data-form-set-send="fetch" to stay on the page, or hands the values to a listener on formset:submit.