Knowledge Base Your audience Style and customize a form

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 gets aria-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-field set to the field’s name, so they hide together. Add hidden so 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 the service answer contains “Other”. A rule set in RadarSend takes priority.

  • Don’t set display on it. A display style, 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=2 to 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 display on it. Add the [hidden] rule from Conditional fields in your own markup.
  • My sonarsend:ready code never runs. That event fires once, early. Put your listener in a script above the forms.js tag.

Reference#

Attributes#

AttributeGoes onWhat it does
data-sonarsend-formthe <form>Connects the form to RadarSend. Required.
nameeach inputWhich field the answer is saved to.
data-sonarsend-error-forany element in the formWhere that field’s errors appear. Use _form for errors that aren’t about one field.
data-sonarsend-fieldan element around a fieldHides and shows everything inside with that field.
data-show-when="field:value"an inputShows it only when field contains value.
data-loadingthe submit buttonThe 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 formYour own success message.
data-sonarsend-messageinside that templateWhere the success message text goes.

Classes#

ClassGoes onWhen
.sonarsend-erroran error messageUntil the form’s sent again.
.sonarsend-successthe success messageAfter the form’s sent.
.sonarsend-submittingthe <form>While it’s sending.
.sonarsend-submittedthe <form>After RadarSend shows the success message.

Events#

Each event fires on the form. preventDefault() stops what RadarSend would do next.

EventWhene.detailpreventDefault()
sonarsend:readyThe form has loadedconfig: the form’s fields and their optionsDoes nothing
sonarsend:submitJust before sendingdata: the answersStops the form being sent
sonarsend:successThe form was sentmessage, redirectUrl, dataStops the success message and redirect
sonarsend:errorA required field is empty, or sending failedmessage, field, fields (every field with a problem), status (null for a required-field check)Stops the error message