Style and customize a form
RadarSend’s form script, forms.js, sends your form and checks required fields. It doesn’t add any styling, so the form looks however your CSS says. This page covers styling it and changing how it behaves. If you haven’t made the form yet, start with Create and embed a form.
Style it#
Style the form like any other HTML. The script adds a few things you’ll want to style too:
- Error messages have the class
.sonarsend-error. The field with the problem getsaria-invalid="true". - The success message has the class
.sonarsend-success. - While it’s sending, the form has the class
.sonarsend-submitting. - After it’s sent, the form has the class
.sonarsend-submitted.
.sonarsend-error { display: block; margin-top: 8px; font-size: 14px; color: #b3261e; }
[aria-invalid="true"] { border-color: #b3261e; }
.sonarsend-success { font-size: 18px; padding: 24px 0; }
Choose where error messages go#
An error message appears right after its field. To put it somewhere else, add an empty element with data-sonarsend-error-for set to the field’s name:
<p class="field-error" data-sonarsend-error-for="service"></p>
Add one with data-sonarsend-error-for="_form" for problems that aren’t about a single field, like a dropped connection. Otherwise those appear after the submit button.
You’ll want one for every group of radio buttons. Without it, the message lands inside the first option’s label.
Use chips or radio buttons#
You don’t have to use the <select> from the snippet. Radio buttons work too, and you can style them as chips:
<fieldset>
<legend>What do you need?</legend>
<label class="chip"><input type="radio" name="service" value="Website"><span>Website</span></label>
<label class="chip"><input type="radio" name="service" value="Branding"><span>Branding</span></label>
<label class="chip"><input type="radio" name="service" value="Other"><span>Something else</span></label>
<p class="field-error" data-sonarsend-error-for="service"></p>
</fieldset>
.chip { position: relative; display: inline-block; padding: 10px 16px; border: 1px solid #ccc; cursor: pointer; }
.chip input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.chip:has(input:checked) { background: #1a1a1a; color: #fff; }
.chip:has(input:focus-visible) { outline: 2px solid #1a1a1a; outline-offset: 2px; }
Each value has to match one of the field’s options exactly, capital letters included. The text people see can say anything. The last chip above says “Something else” but sends Other.
Copy the values from the <option>s in your snippet. If the field is set to Restrict to these values, anything else is turned away with an error. If it isn’t, a misspelled value gets saved as a new option.
Show your own thank-you message#
After someone sends the form, RadarSend replaces it with your Success message. There are three ways to change that.
Go to a thank-you page. Set a Redirect URL in the form’s settings. No code needed.
Design the message. Put a <template data-sonarsend-success> inside the form. It’s shown in place of the plain text, with your success message in the element marked data-sonarsend-message:
<template data-sonarsend-success>
<h3>Thanks, we'll be in touch.</h3>
<p data-sonarsend-message></p>
</template>
Show your own panel. Listen for the sonarsend:success event and call preventDefault(). RadarSend then leaves the page alone and doesn’t redirect, so you can show whatever you like:
<div id="thanks" hidden>
<h3>Thanks, we'll be in touch.</h3>
<a href="#" id="send-another">Send another</a>
</div>
const form = document.querySelector('[data-sonarsend-form]');
const thanks = document.getElementById('thanks');
form.addEventListener('sonarsend:success', (e) => {
e.preventDefault();
form.hidden = true;
thanks.hidden = false;
});
For a “Send another” link, reset the form and show it again. A reset doesn’t re-check which conditional fields should show, so give it a nudge with a change event:
document.getElementById('send-another').addEventListener('click', (e) => {
e.preventDefault();
form.reset();
form.querySelector('input[name="service"]')
.dispatchEvent(new Event('change', { bubbles: true }));
thanks.hidden = true;
form.hidden = false;
});
If you want the success message’s text, use e.detail.message.
Add your own checks#
For simple cases, turn on Required in RadarSend. For anything else, like “fill in this box if you picked Other” or “write at least a sentence,” listen for sonarsend:submit. It fires just before the form is sent. Call preventDefault() to stop it:
function check() {
const picked = form.querySelector('input[name="service"]:checked');
if (!picked) return 'Pick what you need help with.';
if (picked.value === 'Other' && !form.elements.other_service.value.trim()) return 'Tell us what you need.';
if (form.elements.details.value.trim().length < 30) return 'Tell us a bit more about the project.';
return null;
}
form.addEventListener('sonarsend:submit', (e) => {
const problem = check();
if (problem) {
e.preventDefault();
showError(problem); // your own function
}
});
Show the first problem first#
RadarSend checks Required fields before your code runs. If one’s empty, it shows its own message, even when an unanswered question further up the page should come first. To keep messages in order, catch RadarSend’s message and show yours instead:
form.addEventListener('sonarsend:error', (e) => {
if (e.detail.status !== null) return;
e.preventDefault();
showError(check() || e.detail.message);
});
e.detail.status is null when the error comes from the required-field check. Other errors, like a dropped connection, have a status, and this code leaves them to RadarSend.
Keep the form on the page after sending#
This suits a newsletter signup in a footer. Add data-success="keep", and the form clears and stays put. The success message appears where your template is, or at the end of the form:
<form data-sonarsend-form="frm_abc123" data-success="keep">
<input type="email" name="email" placeholder="you@example.com">
<button type="submit">Join</button>
<template data-sonarsend-success>
<strong>You're on the list.</strong>
</template>
</form>
Show that it’s sending#
While the form is sending, the button is disabled. To change its text, add data-loading:
<button type="submit" data-loading="Sending…">Send</button>
To fade the whole form:
.sonarsend-submitting { opacity: .6; pointer-events: none; }
Conditional fields in your own markup#
If you set up a conditional field in RadarSend, the snippet already handles it. If you write your own markup:
-
Wrap the field. Put its label, input and any help text in one element with
data-sonarsend-fieldset to the field’s name, so they hide together. Addhiddenso it doesn’t flash on screen while the page loads:<div data-sonarsend-field="other_service" hidden> <label for="other-service">What do you need?</label> <input id="other-service" name="other_service" type="text"> </div> -
Or set the rule in your HTML.
data-show-when="service:Other"on an input shows it when theserviceanswer contains “Other”. A rule set in RadarSend takes priority. -
Don’t set
displayon it. Adisplaystyle, inline or from a class, keeps the field on screen when it should be hidden. If your layout needs one, add this rule:[data-sonarsend-form] [hidden] { display: none !important; }
Troubleshooting#
- The built-in message shows instead of my own panel. Your browser has an older copy of the script. Add
?v=2to the end of the script’s address to load a fresh one, and raise the number if it happens again. - An error shows up inside the first radio button’s label. Give the group its own spot for errors. See Choose where error messages go.
- A hidden field is still showing. Something’s setting
displayon it. Add the[hidden]rule from Conditional fields in your own markup. - My
sonarsend:readycode never runs. That event fires once, early. Put your listener in a script above theforms.jstag.
Reference#
Attributes#
| Attribute | Goes on | What it does |
|---|---|---|
data-sonarsend-form | the <form> | Connects the form to RadarSend. Required. |
name | each input | Which field the answer is saved to. |
data-sonarsend-error-for | any element in the form | Where that field’s errors appear. Use _form for errors that aren’t about one field. |
data-sonarsend-field | an element around a field | Hides and shows everything inside with that field. |
data-show-when="field:value" | an input | Shows it only when field contains value. |
data-loading | the submit button | The button’s text while sending. |
data-success="keep" | the <form> | Keeps the form on the page after it’s sent. |
<template data-sonarsend-success> | inside the form | Your own success message. |
data-sonarsend-message | inside that template | Where the success message text goes. |
Classes#
| Class | Goes on | When |
|---|---|---|
.sonarsend-error | an error message | Until the form’s sent again. |
.sonarsend-success | the success message | After the form’s sent. |
.sonarsend-submitting | the <form> | While it’s sending. |
.sonarsend-submitted | the <form> | After RadarSend shows the success message. |
Events#
Each event fires on the form. preventDefault() stops what RadarSend would do next.
| Event | When | e.detail | preventDefault() |
|---|---|---|---|
sonarsend:ready | The form has loaded | config: the form’s fields and their options | Does nothing |
sonarsend:submit | Just before sending | data: the answers | Stops the form being sent |
sonarsend:success | The form was sent | message, redirectUrl, data | Stops the success message and redirect |
sonarsend:error | A required field is empty, or sending failed | message, field, fields (every field with a problem), status (null for a required-field check) | Stops the error message |