Adding Barba.js to the WordPress Framework

So you want barba.js to handle smooth transitions in your WordPress site? Barba.js makes using a website feel like a smooth experience by allowing you to add transition based animations and there are a ton of details (like where in the transition) that allow it to be very powerful. And for a simple html site that you’re coding by hand, I think it’s a really simple library to implement. Integrating Barba.js into WordPress, however, comes with some challenges. I’ll walk you through how to implement barba.js into your WordPress functions, template files.

This will be my third time building a site using barba.js and WordPress. humnlab.com uses barba to control home page to project page animations and a bunch of page specific animations.

Prepping the WordPress canvas for Barba.js

I’m building a fresh theme based on UnderScore. The barba.js WordPress build is my high end client base build, so it’s the starting point for me. I’m a bit old school and still remember all the classes from Bootstrap and am comfortable with their grid, so I’ll be adding bootstrap to the mix as well.

Understanding the Template Structure for WordPress

The fundamentals are important and WordPress has great documentation. So go here for the definitive WordPress template structure document.

All you really need to know for this, IMO, is that WordPress chunks out a single page experience into 3 files. A top, middle and bottom file. The top and bottom files are always the header.php and footer.php files respectively. Header files are in charge of meta data, style and script files and footer files are usually a bunch of javascript files.

The middle file can change, it can be a template file, post file or a page file and within those you can call on a lot of templates. It will be these middle files where we will incorporate barba.js

Understanding where barba goes in the template structure

my-theme/
├── style.css
├── functions.php                 # Enqueue Barba.js, GSAP, Bootstrap grid
├── index.php
├── header.php                    # add the wrapper data attribute
├── footer.php
├── sidebar.php
├── screenshot.png
├── front-page.php                # Barba container markup for front page
├── page.php                      # Barba container markup for page
├── single.php                    # Barba container markup for post
├── archive.php                   # Barba container markup for archive
├── search.php
├── 404.php                       # Barba container markup for error page
├── assets/
│   ├── css/
│   │   ├── main.css
│   │   ├── bootstrap-grid.css    # Bootstrap grid only
│   │
│   ├── js/
│   │   ├── main.js               # Your main theme scripts
│   │   ├── barba.min.js          # Barba.js library
│   │   ├── barba-init.js         # Your Barba initialization/config
│   │   ├── gsap.min.js           # GSAP library
│   └── images/
├── inc/
└── template-parts/
diagram for integrating barba.js into the WordPress template structure for any given page.

Adding Barba.js to the WordPress functions.php file

Before we start adding the wrapper and container barba data attributes, we need to include barba in our theme files. To install you’ll need to know how to use your commandline interpreter and a package manager. That to me, seems like a huge pain in the ass, generally how I feel about package managers. So you can go here an download the barba.js file here and add it to your javascript assets folder.

Create a file called barba-init.js in the same folder. This is where all your barba.js transition rules will live.

For this project, i’m adding GSAP for animations, it’s well established, but new to me, I’ll be using AI to build out simple animation builds for me. I’ve used anime.js, and lottie.js for more complex animations, both great.

Click the get GSAP button in the link above, pull the gsap.min.js file and add it to your js assets.

functions.php code – enqueue scripts for barba and animations in WordPress

//Enqueue scripts and styles
function clickfoundrytemplate_scripts() {
	// Enqueue Bootstrap Grid
	wp_enqueue_style('bootstrap-grid', get_template_directory_uri() . '/assets/css/bootstrap-grid.min.css', array(), '5.3.3');

	wp_enqueue_style( 'clickfoundrytemplate-style', get_stylesheet_uri(), array(), _S_VERSION );
	wp_style_add_data( 'clickfoundrytemplate-style', 'rtl', 'replace' );

	wp_enqueue_script( 'clickfoundrytemplate-navigation', get_template_directory_uri() . '/assets/js/navigation.js', array(), _S_VERSION, true );
	// barba.js
	wp_enqueue_script('barba-js', get_stylesheet_directory_uri() . '/assets/js/barba.min.js', false, '', true);
	wp_enqueue_script('gsap-js', get_stylesheet_directory_uri() . '/assets/js/gsap.min.js', false, '', true);
	//barba init.js
    wp_enqueue_script('barba-init-js', get_stylesheet_directory_uri() . '/assets/js/barba-init.js', false, '3.9', true);

	if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
		wp_enqueue_script( 'comment-reply' );
	}
}
add_action( 'wp_enqueue_scripts', 'clickfoundrytemplate_scripts' );

Barba Wrappers and Containers in WordPress files

barba is a library for single page applications and when used properly it can save apps a lot of time by opting out of loading scripts, navs, style sheets that are loaded sitewide. Most websites share a lot of that code and still reload it with every new page with only some of the content being unique to individual pages.

So, let’s call the parts that are new to each page the container. The part outside of the container, the repeatable information, let’s call that the wrapper.

This is what that would look like in some basic html boiler plate with the Barba.js structure:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Barba Starter</title>
  <meta name="description" content="Barba.js + GSAP starter page." />
  <link rel="stylesheet" href="/assets/css/main.css" />
</head>
<body data-barba="wrapper">
    <header role="banner">
      <!-- Header -->
    </header>
    <main id="content" role="main"
          data-barba="container"
          data-barba-namespace="home">
      <h1>Home</h1>
      <p>Content for this view.</p>
    </main>
    <footer role="contentinfo">
      <!-- Footer -->
    </footer>
  <script src="/assets/js/gsap.min.js" defer></script>
  <script src="/assets/js/barba.min.js" defer></script>
  <script src="/assets/js/barba-init.js" defer></script>
</body>
</html>

WordPress Header.php with Barba.js

The body tag will be found in the header.php file. You don’t have to do it this way, but for me using _s, this works well for me. For most theme files, definitely true for _S, the body tag will be found in the header.php

<?php
/**
 * The header for our theme
 *
 * This is the template that displays all of the <head> section and everything up until <div id="content">
 *
 * @link https://developer.wordpress.org/themes/basics/template-files/#template-partials
 *
 * @package clickfoundryTemplate
 */

?>
<!doctype html>
<html <?php language_attributes(); ?>>
<head>
	<meta charset="<?php bloginfo( 'charset' ); ?>">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="profile" href="https://gmpg.org/xfn/11">

	<?php wp_head(); ?>
</head>

<body <?php body_class(); ?> data-barba="wrapper">
<?php wp_body_open(); ?>

WordPress index.php with Barba.js

On to the container portion. Barba needs two data attributes: data-barba and data-barba-namespace. I think technically, you don’t need the second, but your life will be much easier if you include it on template pages, either native ones to the theme or ones that you create.

<?php
/**
 * The main template file
 *
 * This is the most generic template file in a WordPress theme
 * and one of the two required files for a theme (the other being style.css).
 * It is used to display a page when nothing more specific matches a query.
 * E.g., it puts together the home page when no home.php file exists.
 *
 * @link https://developer.wordpress.org/themes/basics/template-hierarchy/
 *
 * @package clickfoundryTemplate
 */

get_header();
?>

<main id="primary" class="site-main container-xxl"  data-barba="container" data-barba-namespace="<?php echo get_post_type(); ?>"> <!--this is where the barba container starts-->
  <div class="row">
    <div class="col-12 col-lg-8">
   <?php
    if ( have_posts() ) :
    if ( is_home() && ! is_front_page() ) :
   ?>
<header>
<h1 class="page-title screen-reader-text"><?php single_post_title(); ?></h1>
</header>
<?php
  endif;
  /* Start the Loop */
  while ( have_posts() ) :
  the_post();

    //Include the Post-Type-specific template for the content.
    get_template_part( 'template-parts/content', get_post_type() );
    endwhile;
      the_posts_navigation();
    else :
      get_template_part( 'template-parts/content', 'none' );
    endif;
    ?>
    </div>
    <div class="col-12 col-lg-4">
      <?php get_sidebar();?>
    </div>
  </div>
</main><!-- #main barba container ends -->

<?php
get_footer();

You can take a look at these sample files in their entirety on my github page.

Big shout out to Fjobeir’s barba post, who helped me understand this my first go around 4 years ago.

Making a transition with Barba on WordPress (barba-init.js)

So, I made this ketchup and mustard transition on my local build to test it out.

There are different stages in the barba transition lifecycle that you can hook into, but for simplicity I chose leave and enter. I used Claude to give me the GSAP animation code, but after looking at GSAP seems really nice. So the opacity of everything in the barba container fades out, the background attribute in the body element goes to red when leaving. When entering, we set the container opacity at 0, the body color to orange than white and then we increase the container opacity to 1. Check it out.

document.addEventListener("DOMContentLoaded", function() {
  barba.init({
    transitions: [{
      name: 'basic-transition',
        leave(data) {
        // Clean up namespace classes
        document.body.className = document.body.className.replace(/\bnamespace-\S+/g, '').trim();
        
        // Create GSAP timeline for leave animation
        const tl = gsap.timeline();
        
        // Animate content opacity to 0 and background to red simultaneously
        tl.to(data.current.container, {
          opacity: 0,
          duration: 0.5,
          ease: "power2.inOut"
        })
        .to(document.body, {
          backgroundColor: "#ff0000", // red
          duration: 0.5,
          ease: "power2.inOut"
        }, 0); // Start at the same time as opacity animation
        
        return tl;
      },
      
      enter(data) {
        const namespace = data.next.namespace;
        document.body.classList.add(`namespace-${namespace}`);
        
        // Set initial state for entering content
        gsap.set(data.next.container, { opacity: 0 });
        
        // Create GSAP timeline for enter animation
        const tl = gsap.timeline();
        
        // Background color sequence: red → orange → white
        tl.to(document.body, {
          backgroundColor: "#ffa500", // orange
          duration: 0.3,
          ease: "power2.inOut"
        })
        .to(document.body, {
          backgroundColor: "#ffffff", // white
          duration: 0.4,
          ease: "power2.inOut"
        })
        // Then fade in content
        .to(data.next.container, {
          opacity: 1,
          duration: 0.5,
          ease: "power2.inOut"
        });
        
        return tl;
      }
    }]
  });
});

[Video: Screen-Recording-2025-08-15-at-3.02.27-PM.mov]

Now comes the issues with incorporating barba.js with WordPress

The barba.js library and WordPress in a lot of ways are at odds with each other. WordPress is dynamically creating single pages and it’s highly adaptable because of the built in plugin community. Barba.js is dependent on stability in the gap between the wrapper and the container divs and that just isn’t guaranteed without custom development of workarounds.

There is a great group of people working on this library, so if you’re stuck look for their slack channel or discord. I can try and help, if you reach out. Here is my list of issues:

Scripts need to re-initialize, but not double up on page transitions

Sometimes a certain page will have a plugin that no other page does. It’s actually good SEO practice to only have what you want running at any given time, so during page transitions, things can break because the required files aren’t present since those files are in the gap between the wrapper and the container.

You can manage that by tracking namespaces and making sure your loading the scripts that need to be loaded by removing and adding the files, but that gets tedious.

Some devs get clever and track which scripts are already loaded so they only fetch new ones during transitions. Smart in theory—no duplicates, parallel loading—but that skips a critical step: re-initializing scripts that are already in memory but tied to DOM elements you just replaced.

Example: Contact Form 7’s file might be loaded site-wide, but if you click into a page with a form, the form still needs its init() call. If that doesn’t happen, the form looks fine but won’t submit.

I’m just keeping a list of plugins or libraries that have re-init setups and having running a dom script to see if i have to load them by sniffing out plugin/library specific classes.

if (document.querySelector('.wpcf7-form')) {
    // Element exists
    wpcf7.init();
}

Can’t think of other issues, leave some in the comments and maybe I’ll add them here.

💬