Skip to content

Everything for WordPress, web development — and beyond

🔄 jQuery .toggle(): modern toggle and replacement for the removed method

🔄 jQuery .toggle(): modern toggle and replacement for the removed method

Found a ready-made jQuery snippet for a "Show/Hide" button that doesn't work? Code using .toggle() was written following a decade-old manual and silently broke back in jQuery 1.9. For a beginner, this situation looks like black magic: the console is empty, no errors, the button simply doesn't respond to a second click.

The problem is that the .toggle() method in jQuery had two lives. The first, animating show/hide of elements, is still alive. The second, alternating actions on click, was removed in 2013, but search engines still surface old articles as top results. Here we'll explain why the old code is dead and write three working alternatives, from a simple flag to data attributes for a dozen buttons at once.

💡 Quick overview:

  • Why your .toggle(fn1, fn2) doesn't work and where to look for deprecated code
  • Method 1: a state variable for a single button with minimal code
  • Method 2: .data() when you have many buttons and each lives its own life
  • Bonus: three or more actions in a click chain using a counter
  • Modern .toggle() for animation, what remains and how to use it

What is.toggle() in jQuery and why old code is dead

Before jQuery 1.8, the .toggle() method could do two different things depending on the arguments passed. If you passed two function parameters, it worked as a toggle: the first click ran the first function, the second click ran the second, the third click ran the first again, and so on in a cycle.

It looked compact:

1// Worked in jQuery before 1.8, removed in 1.9
2$('#myButton').toggle(
3 function() {
4 $(this).text('Show categories').css('background', 'green');
5 },
6 function() {
7 $(this).text('Hide categories').css('background', 'red');
8 }
9);

Exactly this code has been circulating in Russian-language blogs to this day. The problem is that with the release of jQuery 1.9 (January 2013), this form of .toggle() was completely removed. The reason: the dual purpose of the method created confusion, and beginners couldn't understand why .toggle() sometimes animated and sometimes toggled actions.

Today .toggle() does only one thing: shows or hides an element with animation. For alternating actions, the developer needs to explicitly manage state.

Method 1: state variable

The most direct approach is to create a boolean variable and toggle it in the .click() handler. Suitable for a single button or elements with shared toggle logic.

Add the code to your theme scripts file (via wp_enqueue_script in functions.php) or in a <script> block before the closing </body>:

1let isFirstClick = true;
2
3$('#toggleBtn').on('click', function() {
4 const $btn = $(this);
5
6 if (isFirstClick) {
7 $btn.text('Hide categories');
8 $('#contentBlock').show(700);
9 isFirstClick = false;
10 } else {
11 $btn.text('Show categories');
12 $('#contentBlock').fadeOut('slow');
13 isFirstClick = true;
14 }
15});

Here isFirstClick stores the current state outside the handler. The first click shows the block and sets the flag to false, the second hides it and returns the flag back. No magic: you know exactly which action will happen now and which will happen next.

The advantage of this approach is that the flag can be reset from anywhere in the program. For example, after an AJAX request you want to return the button to its initial state, just use isFirstClick = true.

The disadvantage is one variable for all buttons. If the page has two independent toggles with the class .toggleBtn, the isFirstClick variable will be shared by both. For such a scenario, use method 2.

Method 2: jQuery data attributes

When you have several independent buttons (for example, each product in a catalog has its own specifications block), you need to store state directly on the DOM element. The .data() method attaches arbitrary data to an element without polluting the global scope.

Load the script the same way, via wp_enqueue_script or in the footer:

1$('.toggle-btn').on('click', function() {
2 const $btn = $(this);
3 const hasClicked = $btn.data('clicked') || false;
4
5 if (!hasClicked) {
6 $btn.text('Hide specifications').css('background', '#f0f0f0');
7 $btn.next('.specs-block').show(400);
8 $btn.data('clicked', true);
9 } else {
10 $btn.text('Show specifications').css('background', '#fff');
11 $btn.next('.specs-block').fadeOut(300);
12 $btn.data('clicked', false);
13 }
14});

Calling $btn.data('clicked') reads the state of this specific button. Ten buttons on the page means ten isolated states. No conflicts.

Note: .data() works with jQuery's memory, not with HTML data-* attributes. If you need the state to survive a page reload, additionally write $btn.attr('data-clicked', 'true') and read via .attr().

Bonus: three or more actions in a chain

What if there should be not two but three clicks? For example: first press shows the block, second changes its content, third hides it. Here, instead of a boolean flag, you create a numeric counter:

1$('#multiBtn').on('click', function() {
2 const $btn = $(this);
3 let count = $btn.data('clickCount') || 0;
4
5 switch (count) {
6 case 0:
7 $btn.text('Step 1: show block');
8 $('#targetBlock').show(500);
9 break;
10 case 1:
11 $btn.text('Step 2: load data');
12 $('#targetBlock').load('/ajax-content');
13 break;
14 case 2:
15 $btn.text('Step 3: hide');
16 $('#targetBlock').fadeOut(400);
17 count = -1; // next click will become case 0 again
18 break;
19 }
20
21 $btn.data('clickCount', count + 1);
22});

Resetting the counter (count = -1) makes sense if the chain should loop. If the action is one-time, simply remove the reset, and nothing will happen after the third click.

Modern.toggle() for animation

The form of .toggle() that survived is strictly responsible for element visibility. Without arguments, it's instant toggling. With duration, it animates width, height, and opacity simultaneously.

Basic scenario: a "Show/Hide menu" button:

1$('#menuToggle').on('click', function() {
2 $('#mainMenu').toggle('slow', function() {
3 // callback after animation completes
4 console.log('Animation complete');
5 });
6});

The strings 'fast' and 'slow' give 200 and 600 milliseconds respectively. You can pass an exact number: .toggle(400) means 400 ms.

A useful trick: forcing state via a boolean parameter. .toggle(true) always shows the element, and .toggle(false) always hides it. This is convenient in combination with an external flag from method 1:

1let visible = true;
2$('#toggleBtn').on('click', function() {
3 visible = !visible;
4 $('#content').toggle(visible);
5});

The code reads linearly: the flag is flipped, the element takes the required state. No conditional if/else around visibility.

⁉️🤔 Frequently asked questions

Why doesn't my old code with .toggle(fn1, fn2) show errors in the console?

The old form of .toggle() was removed at the method level: jQuery simply doesn't find such a signature and silently does nothing. The console is empty because calling .toggle(fn1, fn2) for modern jQuery is a syntactically correct but meaningless call (arguments are ignored). Check your jQuery version: if it's 1.9 or higher, the old code is dead.

Can I include jQuery Migrate and not rewrite the code?

Technically, yes. The jQuery Migrate plugin restores removed methods, including the old .toggle(). But this is a temporary crutch: Migrate adds ~10 KB minified and is designed for a transition period, not permanent use. If the site runs on WordPress and jQuery loads from core, Migrate is already there for backward compatibility. But relying on it in your own code is not recommended: it can be disabled at any moment by a theme or plugin update.

Which is better, a variable or a data attribute?

For a single button, use a variable (less code). For several independent buttons or dynamically added elements, use .data() (state isolation). If buttons are added via AJAX after page load, use delegation: $(document).on('click', '.toggle-btn', function() { ... }).

Does.toggle() work on mobile devices?

Yes. Modern .toggle() (animation) and all click handlers from this article work correctly on touch events. jQuery normalizes click for mobile browsers since its earliest versions. The only nuance: on iOS before version 12, elements without cursor: pointer had a delayed click, but this problem is solved with one CSS line touch-action: manipulation for the button.

Where should I place jQuery code in WordPress?

Three options. Quick: the Code Snippets plugin: insert JS code into a snippet with type "JavaScript," and it automatically loads in the footer. Proper: create a file js/toggle.js in your child theme, register it via wp_enqueue_script('my-toggle', get_stylesheet_directory_uri() . '/js/toggle.js', array('jquery'), '1.0', true) in functions.php. The true parameter at the end sends the script to the footer, which is standard for code that works with the DOM. Don't put <script> directly in the post body; the WordPress editor will strip the tag.

Which approach to choose for your project

If you have one button with text toggling and block visibility, use a state variable. Three lines, transparent behavior, trivial debugging.

If there are multiple toggles (catalog, settings panel, list of expandable items), use .data() only. A global variable for multiple buttons will create a state race condition that you'll debug longer than it takes to write the code using method 2.

A chain of three or more steps requires a counter with switch. Don't complicate with boolean flags what naturally fits with numbers.

Most importantly, if you find an article in search results with .toggle(fn1, fn2), close the tab. jQuery is alive, but that specific method was buried in 2013.

You can refresh your knowledge on running JavaScript snippets on the fly in our article about working with snippets in Chrome DevTools, which will be useful for quickly testing code from this article.