Skip to content

Everything for WordPress, web development — and beyond

🔧 How to replace the Contact Form 7 preloader: custom spinner, centering, and CSS animation

🔧 How to replace the Contact Form 7 preloader: custom spinner, centering, and CSS animation

The standard Contact Form 7 spinner is instantly recognizable at first glance. A gray arrow, endless rotation, and it's immediately obvious: the site runs on default settings. The design is ten years old, and the preloader is twenty.

But you don't have to put up with this. The spinner replacement mechanism is built into the plugin's CSS framework itself, without editing source files, without risk of losing changes during auto-updates. Three snippets, five minutes, and the form looks exactly as intended in the mockup.

💡 Quick overview:

  • Hide the stock animation via display: none on .wpcf7-spinner::before
  • Add your own GIF as background or draw a spinner with pure CSS
  • Center the preloader via flexbox, without !important and hacks
  • Grab a ready-made animation from loading.io, Icons8, or CSS Loaders
  • For versions below five, the deprecated hook wpcf7_ajax_loader in functions.php

How the CF7 preloader works now

Contact Form 7 is currently at version 6.1.6, and the spinner mechanics haven't changed since version five. No more <img src="ajax-loader.gif">. The browser draws the arrow itself via ::before inside <span class="wpcf7-spinner">. No external files, instant rendering.

The old selector div.wpcf7 .ajax-loader is dead. All instructions from the CF7 4.6 era belong in the archive. Now everything is simpler: override the background on .wpcf7-spinner, hide the pseudo-element, done.

This also eliminated the need for the wpcf7_ajax_loader hook. No more functions.php to replace the arrow, pure CSS, three lines.

Changing the spinner via CSS: step by step

Let's say you have custom-spinner.gif in the images folder of your child theme. Two steps, and the stock arrow disappears.

First, disable the built-in animation. The selector targets precisely the pseudo-element, nothing else gets affected:

1/* Hide default CF7 spinner */
2.wpcf7-spinner::before {
3 display: none;
4}

Now attach your own GIF as background to the <span> itself:

1/* Insert custom preloader */
2.wpcf7-spinner {
3 background-image: url('images/custom-spinner.gif');
4 width: 24px;
5 height: 24px;
6 margin-left: 10px;
7 background-size: contain;
8 background-repeat: no-repeat;
9 background-position: center;
10}
Default Contact Form 7 preloader, gray arrow

Both blocks go into style.css of your child theme. Path: "Appearance → Theme File Editor" or "Appearance → Customize → Additional CSS". Don't touch the plugin files themselves; your changes will be lost at the first update.

Adjust width and height to match your GIF size. Transparent spinner on a dark form? Remove background-color; it's not needed there.

CSS animation without files: pure ring

GIF requires an HTTP request and pixelates on retina displays. The modern approach is to draw the spinner using the browser. border plus @keyframes deliver perfect sharpness at any resolution. No files, instant loading, full control over color.

Here's a double ring with an accent brand color:

1/* Hide default arrow */
2.wpcf7-spinner::before {
3 display: none;
4}
5
6/* Draw outer ring */
7.wpcf7-spinner {
8 width: 24px;
9 height: 24px;
10 border-radius: 50%;
11 display: inline-block;
12 border-top: 2px solid #262b2e;
13 border-right: 2px solid transparent;
14 box-sizing: border-box;
15 animation: cf7-spin 1s linear infinite;
16 background-color: inherit;
17}
18
19/* Inner ring — accent color */
20.wpcf7-spinner::after {
21 content: '';
22 box-sizing: border-box;
23 position: absolute;
24 left: 0;
25 top: 0;
26 width: 24px;
27 height: 24px;
28 border-radius: 50%;
29 border-bottom: 2px solid #FF3D00;
30 border-left: 2px solid transparent;
31}
32
33@keyframes cf7-spin {
34 0% { transform: rotate(0deg); }
35 100% { transform: rotate(360deg); }
36}

Use your own colors. Replace #262b2e and #FF3D00 with your brand pair. Line thickness is 2px, full rotation in one second. Want it faster? Reduce 1s to 0.6s. Slower? 1.5s.

Centering the preloader

By default, the spinner hugs the left edge of the submit button. On mobile width this goes unnoticed, but on desktop it looks sloppy. Solved with flexbox in two rules:

1/* Flex container for the form */
2.wpcf7 form {
3 display: flex;
4 flex-wrap: wrap;
5 justify-content: center;
6}
7
8/* Centering the spinner */
9.wpcf7-spinner {
10 margin: 10px auto 0 auto;
11}

The first rule turns the form into a flex container with center alignment. The second gives the spinner top margin and auto margins to push it to the middle of the row. No float: inherit !important, no negative margin-bottom; everything stays clean and doesn't break when switching themes.

If the form is complex and flex-wrap: wrap breaks the field layout, wrap the spinner in a separate <div> with text-align: center. But nine times out of ten the flexbox approach is enough.

Where to get ready-made animations

ajaxload.info, which was recommended in all old guides, has shut down. Three live alternatives:

  • loading.io, a spinner constructor. You choose shape, color, size. Export to GIF, SVG, CSS, and APNG. Free options are plenty for a contact form.
  • Icons8 Preloaders, a collection of over a thousand icons: rings, bars, dots, 3D variants. The generator outputs GIF, SVG, and APNG. Some are free with attribution, some are premium.
  • CSS Loaders, pure CSS spinners without a single external file. Pick one you like, copy HTML+CSS, adapt it for .wpcf7-spinner. Perfect for those who fundamentally don't want to load a single kilobyte.
Custom preloaders for Contact Form 7, colorful GIF spinners

Get a spinner with a transparent background; it will fit into a form of any color. Optimal size is 24×24 or 32×32 pixels. Smaller ones flicker, larger ones pull attention away from the submit button.

What about versions below 5.x: deprecated method

If your site is still on CF7 4.x, update the plugin. Seriously. The current version 6.1.6 has closed numerous vulnerabilities, and the old wpcf7_ajax_loader hook may stop working at any moment. The developer doesn't guarantee backward compatibility.

But if updating is impossible (custom code is tightly bound to old hooks), here's the method via functions.php:

1// Replacing CF7 preloader for versions ≤4.x — deprecated
2add_filter('wpcf7_ajax_loader', 'my_wpcf7_ajax_loader');
3function my_wpcf7_ajax_loader() {
4 return get_bloginfo('stylesheet_directory') . '/images/ajax-loader.gif';
5}

The code goes into functions.php of your child theme. The ajax-loader.gif file is uploaded beforehand to images/ inside the child theme. For child theme: stylesheet_directory. For parent theme: template_directory.

And still update at the first opportunity.

Live example on video

The video below shows a complete walkthrough: what the endlessly spinning spinner problem looks like, why old instructions don't work, and how to fix it in two minutes via CSS:

⁉️🤔 Frequently asked questions

Why isn't the CSS applying, the spinner isn't changing?

Almost certainly you're targeting the old selector div.wpcf7 .ajax-loader. Since version five it's useless. You need to target .wpcf7-spinner::before (to hide the default) and .wpcf7-spinner (to attach your own). Check the plugin version: "Plugins → Installed Plugins → Contact Form 7". Open DevTools (F12), find in Elements <span class="wpcf7-spinner"> inside the form. If it has a ::before pseudo-element with a border, you're on the current version; use the selectors from the CSS section. If you see an <img> with class ajax-loader, the plugin is hopelessly outdated.

Can I make the spinner gradient or multicolored?

Yes, without a single pixel of graphics. The basic CSS spinner from the animation section accepts any colors via border-top and border-bottom. For a gradient, replace the solid color with conic-gradient in a mask. Example of a ring with red-yellow-green transition:

1.wpcf7-spinner {
2 background: conic-gradient(#FF3D00, #FFC107, #00C853, #FF3D00);
3 -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 0);
4 mask: radial-gradient(farthest-side, transparent calc(100% - 4px), #000 0);
5 animation: cf7-spin 1s linear infinite;
6}

Works in modern browsers. Ring thickness is set in mask via calc().

How do I completely remove the spinner?

One line: .wpcf7-spinner { display: none !important; }. But think twice. The spinner is the only signal to the user that the form was submitted and the server is processing the request. Without it, the person will click "Submit" again, and you'll get a duplicate submission. It's better not to remove it, but to replace it with a minimalist ring in a neutral color.

Does a custom spinner affect loading speed?

It depends on the method. A 2-5 KB GIF adds one HTTP request, which has virtually no impact on page speed. A CSS spinner adds neither a request nor a byte beyond 300-400 bytes of code (the fastest option). SVG in base64 inside CSS also requires no request, but weighs 1-2 KB. For a contact form, the difference between the three methods is invisible to the eye. CSS animation wins overall: no files, no pixelation, doesn't clutter the media library.

Where should I store the spinner file to avoid losing it during theme updates?

Only in the child theme. Put custom-spinner.gif in the parent theme, and when it updates the file will disappear. Correct structure:

1/wp-content/themes/
2 your-theme/
3 your-theme-child/
4 style.css ← CSS goes here
5 images/
6 custom-spinner.gif ← file goes here

In CSS write the path without a leading slash: url('images/custom-spinner.gif'). This is a relative path from the child theme's style file. Absolute paths like /wp-content/themes/... break when migrating the site to a different domain.

Which replacement method to choose for your case

Three scenarios, in order of increasing complexity and customization options.

Scenario

Method

Files

Effort

Minimal changes, have a ready GIF

CSS with background image

1 GIF

2 minutes

Want perfect sharpness, no file

Pure CSS (border + @keyframes)

0

5 minutes

Need unique animation

CSS gradient or SVG in base64

0

10 minutes

For nine out of ten sites the first scenario is enough. Grab a ready spinner from loading.io, drop it into the child theme, write three lines of CSS, and the default arrow will never give away your site again.