Newsletter inline
The one-line newsletter sign-up of a hero or a footer: an email field and its button in one shape.
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 thenewsletter-inline-*classes of the paste: the shape, the field and the button, large and compact. Restyle them in the Style panel: the line of confirmation takes the background and the colour of your button by itself. The Embed holds what moves, and three colours as variables at the top of its styles,--newsletter-inline-accent,--newsletter-inline-inkand--newsletter-inline-error. The send button is a Link block, because a Webflow submit button is an input and cannot hold the layers that move; the script makes it send the form, and Enter in the field does too. -
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-newsletter-inline Marks the form. It takes no value: leave the value field empty. In the paste it is on the Form inside each Form block. The settings below go on the same element. data-newsletter-inline-line On the shape: the box that holds the email field and the send button side by side. It takes no value. The engine draws the light and the line of confirmation in it. Without it the parent of the field is taken. [ Optional ] data-newsletter-inline-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 not mounted. [ Required ] data-newsletter-inline-hint On the line under the shape: a consent, a promise. It takes no value. The reason takes its place while the address is wrong, then the hint comes back. A form without one gets a line from the engine. [ Optional ] data-newsletter-inline-hide On the label of the field, when it is not shown: it stays in the page for a screen reader. It takes no value. The compact line of the paste has it. [ Optional ] data-newsletter-inline-trap Optional, on one more text field no visitor sees or reaches: a robot that fills it is shown the confirmation and nothing is sent. It takes no value. Not needed in a Webflow Form block, which has its own protection. [ Optional ] data-newsletter-inline-consent Optional, on a checkbox of the form: the address only leaves once it is ticked, and the words of the setting tick show under the shape until then. It takes no value. [ Optional ] data-newsletter-inline-state Written by the engine on the form, several words at once: focus, filled, error, then loading, done or failed. 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-newsletter-inline-send auto How the address leaves once it 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 it without leaving the page and reads the status of the answer. data-newsletter-inline-endpoint empty The address fetch posts to, the one a newsletter service gives. Empty, fetch posts to the action of the form. Options: a full address, https://app.loops.so/api/newsletter-form/...; only read when send is fetch. data-newsletter-inline-sending Sending The word on the button while the address is on its way. Options: any text, in the language of the page. data-newsletter-inline-sent You are in. Check your inbox. The line of confirmation the field and the button become. In a Webflow Form block the words of the Success message are used instead, unless this attribute is written on the form. Options: any text; keep it to one short sentence, it is set on one line, two on a phone. data-newsletter-inline-failed Try again The word on the button when the send failed; the button can be pressed again. Options: any text. data-newsletter-inline-refused That did not go through. The reason under the shape when the send failed. In a Webflow Form block the words of the Error message are used instead, unless this attribute is written on the form. Options: any text. data-newsletter-inline-required Enter your email address. The reason under the shape when it is sent empty. Options: any text. data-newsletter-inline-email This address is not complete. The reason under the shape when what was typed is not an address. Options: any text. data-newsletter-inline-tick Tick the box to subscribe. The reason under the shape when the form has a consent box and it is not ticked. Options: any text. data-newsletter-inline-merge 1100 How long the field and the button take to become one line, in milliseconds: the travel of the button, then the rise of the line of confirmation. The way the address is sentNothing to connect: each line of the paste is a native Webflow Form block, and Webflow receives what it sends. The entries are listed in Site settings, Forms, and the address told of a new entry is set in the same place. Entries only arrive from a published site, its webflow.io address included: the Designer and its preview send nothing. The words of the line of confirmation are those of Webflow's Success message, and the reason of a failed send those of its Error message: select the Form block, open its settings, choose the state and rewrite the words on the canvas. On the published page those two blocks stay out of sight and the form stays in place to become the line. Leave the Action of the form empty, or Webflow receives nothing. To send the address to a newsletter service instead, the attributes below apply, on a form that is not a Webflow Form block.Custom attribute What it does data-newsletter-inline-send="fetch" On the form: posts the values without leaving the page, encoded as a plain form does, with the header Accept: application/json. An answer in the 200s plays the line of confirmation, anything else shows Try again and the reason. It is the setting send, so it may also be set for the whole site. data-newsletter-inline-endpoint="https://..." On the form: where fetch posts, the address a newsletter service gives, https://app.loops.so/api/newsletter-form/YOUR_FORM_ID for Loops. Empty, fetch posts to the action. The service has to accept requests from the page's origin. action="https://..." On the form: the address the form is posted to the usual way, the one Mailchimp or Buttondown gives. 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. name="email" On the field: the name the service files the address under. email for Loops, MERGE0 for Mailchimp; for any other service, the name its own form uses. In Webflow it is the Name of the field, in its settings.
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
NewsletterInline.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-newsletter-inline-send auto How the address leaves once it 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 it without leaving the page and reads the status of the answer. data-newsletter-inline-endpoint empty The address fetch posts to, the one a newsletter service gives. Empty, fetch posts to the action of the form. Options: a full address, https://app.loops.so/api/newsletter-form/...; only read when send is fetch. data-newsletter-inline-sending Sending The word on the button while the address is on its way. Options: any text, in the language of the page. data-newsletter-inline-sent You are in. Check your inbox. The line of confirmation the field and the button become. In a Webflow Form block the words of the Success message are used instead, unless this attribute is written on the form. Options: any text; keep it to one short sentence, it is set on one line, two on a phone. data-newsletter-inline-failed Try again The word on the button when the send failed; the button can be pressed again. Options: any text. data-newsletter-inline-refused That did not go through. The reason under the shape when the send failed. In a Webflow Form block the words of the Error message are used instead, unless this attribute is written on the form. Options: any text. data-newsletter-inline-required Enter your email address. The reason under the shape when it is sent empty. Options: any text. data-newsletter-inline-email This address is not complete. The reason under the shape when what was typed is not an address. Options: any text. data-newsletter-inline-tick Tick the box to subscribe. The reason under the shape when the form has a consent box and it is not ticked. Options: any text. data-newsletter-inline-merge 1100 How long the field and the button take to become one line, in milliseconds: the travel of the button, then the rise of the line of confirmation. Faster, a snap under 600; 0 shows the line of confirmation at once Slower and more deliberate; over 1800 the visitor waits for the answer The way the address is sentOnce the address is right it leaves in one of four ways. With anactionand nothing else, the browser posts the form and the page changes: the way Mailchimp and Buttondown ask for. Withdata-newsletter-inline-send="fetch"it is posted without leaving the page, todata-newsletter-inline-endpointor to the action, and the two become the line of confirmation or the button says Try again, from the status of the answer: the way of Loops, or of an endpoint of your own. With a listener onnewsletterinline:submitthat callspreventDefault(), the page sends the address 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 data-newsletter-inline-send="fetch" On the form: posts the values without leaving the page, encoded as a plain form does, with the header Accept: application/json. An answer in the 200s plays the line of confirmation, anything else shows Try again and the reason. It is the setting send, so it may also be set for the whole site. data-newsletter-inline-endpoint="https://..." On the form: where fetch posts, the address a newsletter service gives, https://app.loops.so/api/newsletter-form/YOUR_FORM_ID for Loops. Empty, fetch posts to the action. The service has to accept requests from the page's origin. action="https://..." On the form: the address the form is posted to the usual way, the one Mailchimp or Buttondown gives. 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. name="email" On the field: the name the service files the address under. email for Loops, MERGE0 for Mailchimp; for any other service, the name its own form uses. In Webflow it is the Name of the field, in its settings. -
Drive it from JavaScript
When the page sends the address itself, or reacts to a subscription.const element = document.querySelector('[data-newsletter-inline]'); element.addEventListener('newsletterinline:sent', (e) => { console.log(e.detail); }); // Send the address yourself: the button waits until done() is called. element.addEventListener('newsletterinline:submit', async (event) => { event.preventDefault(); let ok = false; try { const answer = await fetch('/api/subscribe', { method: 'POST', body: new URLSearchParams({ email: event.detail.email }) }); ok = answer.ok; } catch (error) {} event.detail.done(ok); // true plays the line of confirmation, false shows Try again }); const line = NewsletterInline.mount(element); // the form auto() mounted, or a new one line.set('pam@dundermifflin.com'); // as if it had been typed line.validate(); // shows the reason, returns true when the address can leave line.reset(); // empties it and plays the merge back // When the form leaves the page: NewsletterInline.destroy(element); // removes what the engine added, gives the markup back as it was
-
Fit it to your page
The markup
html <form action="#" method="post" data-newsletter-inline> <label for="news-email">Email address</label> <div data-newsletter-inline-line> <input id="news-email" name="email" type="email" autocomplete="email" placeholder="you@company.com" required> <button type="submit" data-newsletter-inline-submit>Subscribe</button> </div> <p data-newsletter-inline-hint>One letter a month.</p> </form>- The shape is a row:
display: flexon the box markeddata-newsletter-inline-line, the field growing (flex-grow: 1,flex-basis: 0), the button at its own width, a padding round them for the gap, a height on the box. That is all the merge needs; the rest of the look is free. The preview has two, a large one and a compact one, as classes at the end of the stylesheet. - The label is always there. Shown above the shape, or marked
data-newsletter-inline-hide, which keeps it for screen readers. A placeholder is not a label. - The field is an
inputof type email with aname: the name is what the service files the address under (emailfor Loops,MERGE0for Mailchimp). The engine gives it what it lacks:autocomplete="email",inputmode="email",enterkeyhint="send", no capitals and no spelling. - Enter sends, from the field, whatever the button is.
- Several on one page are independent: the preview has two.
Sending the address
Once the address is right it leaves, in one of four ways. The button shows
Sendingand a light runs round the shape meanwhile; then the two become the line of confirmation, or the button saysTry againand the reason shows under the shape.- In Webflow, as a native form. The paste is a real Webflow Form block: the Designer shows Form block, Form, the email field with its own settings, and the two message blocks. Nothing to connect. Webflow receives the entries itself: they are listed in Site settings, Forms, and the address that is told of a new entry is set in the same place. Entries are only received on a published site, the webflow.io address included; the Designer and its preview send nothing. The words of the line of confirmation are those of Webflow's Success message, and the reason of a failed send those of its Error message: select the Form block, open its settings, choose the state and rewrite the words on the canvas. On the published page the two blocks themselves are kept out of sight, read out by a screen reader, and the form stays in place to become the line. 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 the endpoint of a service, without leaving the page. Add
data-newsletter-inline-send="fetch"and the address of the service indata-newsletter-inline-endpoint(or in theactionof the form). The values are posted withfetch, encoded as a plain form does (application/x-www-form-urlencoded;multipart/form-datawhen the form saysenctype="multipart/form-data"), with the form'smethod(POST when none is written) and the headerAccept: application/json. An answer in the 200s is a success; any other, or no answer, a failure. 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 service answers with. The check of the address still plays; the line of confirmation is then the service's page. - By hand. The form fires
newsletterinline:submitbefore it leaves, with{ form, email, done, reset }indetail. CallpreventDefault()on it, send the address yourself, thendetail.done(true)ordetail.done(false); a second argument gives the words,detail.done(false, 'Already subscribed.').detail.reset()empties the line to start again.
The stand-in of the preview
The preview sends nothing. Its page adds one listener to the two files and the markup, which holds the address for a moment, plays the line of confirmation, then starts again. A page that must behave exactly as the preview, with no service connected yet, carries it after the script, in this short form; a real page sends instead, and drops it:
html <script> document.addEventListener('newsletterinline:submit', function (event) { event.preventDefault(); setTimeout(function () { event.detail.done(true); }, 1800); setTimeout(function () { event.detail.reset(); }, 7200); }); </script>The preview page of the library also shows a small control, See it happen, that types a sample address and sends it. It is an aid of that page for whoever is looking, not part of the component: it is in neither the markup nor the stylesheet, and is not to be rebuilt.
The snippet is written with
action="#", a placeholder that sends nothing: replace it, or use one of the other ways. The Webflow paste leaves it out.Two services, exactly
Checked against the documentation of each on 11/10/2026.
Loops, with fetch. Loops documents an endpoint for a form of your own (loops.so/docs/forms/custom-form): a POST to
https://app.loops.so/api/newsletter-form/<YOUR_FORM_ID>, encoded asapplication/x-www-form-urlencoded, the address in a field namedemail; it answers 200 with{ "success": true }, 400 or 500 with a message, and 429 when one visitor sends too often. The form id is in Loops under Forms, Settings.html <form method="post" data-newsletter-inline data-newsletter-inline-send="fetch" data-newsletter-inline-endpoint="https://app.loops.so/api/newsletter-form/YOUR_FORM_ID"> <label for="news-email" data-newsletter-inline-hide>Email address</label> <div data-newsletter-inline-line> <input id="news-email" name="email" type="email" placeholder="you@company.com" required> <button type="submit" data-newsletter-inline-submit>Subscribe</button> </div> </form>Mailchimp, the usual way. Mailchimp documents a form hosted on your own site (mailchimp.com/help/host-your-own-signup-forms): a POST to
https://<account>.<dc>.list-manage.com/subscribe/post, with two hidden fields,u(the user ID) andid(the audience ID), and the address in a field namedMERGE0. The three values are read in the source of the signup form Mailchimp hosts for the audience (Forms, Other forms, Signup form URL, then View Page Source): copy theactionand the two hidden inputs from there. The form is posted the usual way and Mailchimp answers with its own page; that address is not documented as answering afetchfrom another site.html <form action="https://ACCOUNT.usX.list-manage.com/subscribe/post" method="post" data-newsletter-inline> <input type="hidden" name="u" value="YOUR_USER_ID"> <input type="hidden" name="id" value="YOUR_AUDIENCE_ID"> <label for="news-email" data-newsletter-inline-hide>Email address</label> <div data-newsletter-inline-line> <input id="news-email" name="MERGE0" type="email" placeholder="you@company.com" required> <button type="submit" data-newsletter-inline-submit>Subscribe</button> </div> </form>Others. Buttondown asks, in its documentation, that its embed address is never called with
fetch, since a subscriber may have to follow its answer to a CAPTCHA: take the form it gives, with itsactionand its field names, and post it the usual way. For any other service, start from the form the service itself gives and keep itsactionand its field names: the engine needs one field of type email and does not mind what it is called. An endpoint of your own takes either way: answer in the 200s tofetch, or redirect a plain post.The trap and the consent
- A trap for robots is one more text field, marked
data-newsletter-inline-trap, with a name the service does not use. The stylesheet takes it out of sight and the engine out of the tab order and of screen readers. When it comes back filled, the visitor is a robot: the line of confirmation plays, nothing is sent, andnewsletterinline:trappedis fired. A Webflow Form block has its own protection, in Site settings, Forms: leave the trap out.
html <input type="text" name="company-site" tabindex="-1" data-newsletter-inline-trap>- A consent line is the line marked
data-newsletter-inline-hint: write the sentence there. A consent box, when the law or the service asks for a tick, is a checkbox markeddata-newsletter-inline-consentanywhere in the form, in a label with its sentence: the address does not leave until it is ticked, and the reasontickshows under the shape. In Webflow, drop a Checkbox in the Form and add the attribute to its input.
html <label><input type="checkbox" name="consent" data-newsletter-inline-consent> <span>I agree to receive the monthly letter.</span></label>The colours
Three custom properties, set at zero specificity on the form. Give them your own values on the form or on anything above it, in a rule of your own (in Webflow, in the
<style>of the Embed, where they lead the rules):--newsletter-inline-accent: the light that runs round the shape, the tick, the plate that rises in the button under the pointer.--newsletter-inline-ink: the word of the button while that plate is up.--newsletter-inline-error: the rule of the shape and the mark of the reason when the address is wrong.
Everything else is the look of the page. The line of confirmation is the button grown to the whole shape: it has the button's background, and its words the button's colour, read when the address leaves. A dark button on a light page and a light button in a dark footer both work with no setting, as the two of the preview show. The reason under the shape is set in the colour of the text around the form.
From JavaScript
NewsletterInline.mount(form, overrides)returns the instance of a form, mounting it if it was not:{ element, input, settings, set(value), enter(), leave(), validate(), submit(), done(ok, words), reset(), refresh(), destroy() }.setwrites the address as if it had been typed;validateshows the reason and returns whether the address can leave.NewsletterInline.auto(root)mounts every marked form insideroot, and runs once on its own;NewsletterInline.destroy(form)gives the markup back.- Events, all bubbling from the form:
newsletterinline:ready,newsletterinline:invalid(with the reason indetail.reason),newsletterinline:submit(cancelable, see above),newsletterinline:sent(with the address indetail.email),newsletterinline:failedandnewsletterinline:trapped. - The state is on the form as
data-newsletter-inline-state, several words at once:focus,filled,error, thenloading,doneorfailed. - For a screen reader: the field is described by the line under the shape, which is a polite live region, and is marked
aria-invalidwhile wrong; the button readsSending; the line of confirmation is arole="status"that takes the focus once the field is gone, which also closes the keyboard of a phone. - **Under
prefers-reduced-motion: reduce** every state still shows, at once: the travel and the rise are cut to nothing, and the light round the shape stands still as a whole outline while the address is on its way. - On a phone the line keeps its one shape at the width of the screen: the preview gives the field 16 px, under which iOS zooms into a field, a height for a thumb, and the Send key on the keyboard; the line of confirmation may take two lines.
- Without JavaScript the form is a plain form: the label, the field and a button that posts it, with the browser's own check of the address.
- The words of the demo are the demo's: on a real page every word is the user's own, in the page's language, or asked for.
- The shape is a row:
-
Avoid the pitfalls
- **
action="#"sends nothing.** It is the placeholder of the snippet. A form outside a Webflow Form block needs a realaction, anendpointwithsend="fetch", or a listener onnewsletterinline: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-newsletter-inline-submit. A native Webflow Submit button is aninput, which cannot hold the plate, the word and the word that replaces it. The script makes the Link send the form, and Enter in the field does too. A native Submit button marked the same way still sends, and still grows into the line of confirmation, with no word moving on it. - In Webflow the form itself is not laid out. Webflow hides a form once it is sent, and the mechanics keep this one in place with
display: block: put a flex or a grid on the box markeddata-newsletter-inline-line, or on a box inside the form, not on the Form. - **With
fetch, the service must allow the page's origin** (CORS), and answer a status: one that redirects to a thank-you page answers that page instead. Mailchimp and Buttondown are to be posted the usual way. - The field must grow and the button must not, in a flex row: the merge is the button taking the room the field gives up. A field with a fixed
widthcannot give it up. - Give the shape a height, or a
min-height: as the gap round the button closes, a shape sized by its content would lose that gap in height. - **The field keeps a
name**, or its value is not sent, and alabel. - A long line of confirmation is set on two lines at most, then cut by the shape: one short sentence.
- 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
newsletter-inline.cssthe address is checked but nothing shows, and the engine says so in the console.
- **
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
Newsletter inline is an animated form for Webflow and vanilla JavaScript, with no library. Also called newsletter form, newsletter sign up form or email signup form.
The one-line newsletter sign-up of a hero or a footer: an email field and its button in one shape. While the address is on its way the word of the button gives its place to Sending and a light runs round the whole shape, field and button as one. Once it is sent the edge of the button travels over the address, which does not move, until the button is the whole shape; the gap round it closes as it arrives, the line of confirmation rises where the address stood and a tick is drawn where the word of the button was. A wrong address is said under the shape, in place of the hint, and the reason leaves at the keystroke that fixes it, so nothing below ever moves. It is a real form: pasted in Webflow it is a native Form block whose entries land in the site's Forms tab; elsewhere it posts to its action, or with fetch to the endpoint of a newsletter service, or hands the address to the page. A label for screen readers, shown or not, the state read out, a trap for robots and a consent box as options, reduced motion respected, the same one shape down to the width of a phone. No library. Pattern type newsletter signup form, email subscribe input.
Updated
Questions
Paste it: each line arrives as a native Webflow Form block whose Form carries data-newsletter-inline, with the email field and a Link block marked data-newsletter-inline-submit side by side in a box marked data-newsletter-inline-line. Webflow receives the addresses, listed in Site settings, Forms, once the site is published.
Yes. For a service that answers a request from a web page, Loops for one, add data-newsletter-inline-send="fetch" and its address in data-newsletter-inline-endpoint. Mailchimp and Buttondown are posted the usual way, to the action each one gives, with the field named as it asks (MERGE0 for Mailchimp).
In Webflow, rewrite the Success message of the Form block: its words are the line of confirmation the field and the button become. Elsewhere the sentence is the setting sent, written as data-newsletter-inline-sent on the form.
Add one more text field marked data-newsletter-inline-trap. Visitors never see or reach it; when it comes back filled, the form shows its confirmation and sends nothing. A consent checkbox marked data-newsletter-inline-consent can also be required before the address leaves.
It keeps a real label: shown above the line, or marked data-newsletter-inline-hide, which leaves it to screen readers. A wrong address is announced from the line under the field, the confirmation is a status region, Enter sends from the field, and reduced motion shows every state at once.