Gutenberg Logo

Develop a Post Carousel Block using Swiper.js

Here’s how to develop a post carousel block for the Twenty Twenty-Four theme.

We’ll create a custom block that uses the Swiper.js library for the carousel functionality.

1. Create a Child Theme (Recommended)

It’s always best to create a child theme when making modifications so your changes aren’t overwritten when the parent theme is updated.

  1. Create a new folder in wp-content/themes called twentytwentyfour-child.
  2. Inside this folder, create two files:style.css and functions.php.
    • style.css: CSS/* Theme Name: Twenty Twenty-Four Child Theme URI: https://example.com/twentytwentyfour-child/ Description: My custom child theme for Twenty Twenty-Four Author: Your Name Author URI: https://example.com Template: twentytwentyfour Version: 1.0.0 License: GNU General Public License v2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html Text Domain: twentytwentyfour-child */
    • functions.php: PHP<?php add_action( 'wp_enqueue_scripts', 'twentytwentyfour_child_enqueue_styles' ); function twentytwentyfour_child_enqueue_styles() { wp_enqueue_style( 'twentytwentyfour-parent-style', get_template_directory_uri() . '/style.css' ); wp_enqueue_style( 'twentytwentyfour-child-style', get_stylesheet_directory_uri() . '/style.css', array('twentytwentyfour-parent-style'), wp_get_theme()->get('Version') ); } ?>
  3. Activate the child theme from your WordPress admin dashboard (Appearance > Themes).

2. Register the Custom Block

Now, we’ll create the necessary files for our custom block.

  1. Inside your child theme folder (twentytwentyfour-child), create a new folder called blocks.
  2. Inside the blocks folder, create another folder called post-carousel.
  3. Inside post-carousel, create the following files:
    • block.json (Block metadata)
    • index.js (Block JavaScript for editor)
    • editor.scss (Editor-specific styles)
    • style.scss (Frontend and editor styles)
    • render.php (Frontend rendering)

twentytwentyfour-child/blocks/post-carousel/block.json

JSON

{
    "name": "twentytwentyfour-child/post-carousel",
    "title": "Post Carousel",
    "description": "A carousel of your latest posts.",
    "category": "widgets",
    "icon": "images-alt2",
    "apiVersion": 2,
    "keywords": ["posts", "carousel", "slider"],
    "supports": {
        "html": false,
        "align": ["wide", "full"]
    },
    "attributes": {
        "postsToShow": {
            "type": "number",
            "default": 3
        },
        "category": {
            "type": "string"
        }
    },
    "editorScript": "file:./index.js",
    "editorStyle": "file:./editor.css",
    "style": "file:./style.css",
    "render": "file:./render.php"
}

twentytwentyfour-child/blocks/post-carousel/index.js

This file will contain the JavaScript for the block in the editor. We’ll use @wordpress/block-editor, @wordpress/components, and @wordpress/data to build our controls.

JavaScript

import { registerBlockType } from '@wordpress/blocks';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { PanelBody, QueryControls, SelectControl, RangeControl } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { html } from '@wordpress/element';

// Import Swiper React components
import { Swiper, SwiperSlide } from 'swiper/react';
import { Navigation, Pagination } from 'swiper/modules';

// Import Swiper styles
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';

registerBlockType('twentytwentyfour-child/post-carousel', {
    edit: ({ attributes, setAttributes }) => {
        const { postsToShow, category } = attributes;
        const blockProps = useBlockProps();

        const categories = useSelect((select) => {
            const terms = select('core').getEntityRecords('taxonomy', 'category', { per_page: -1 });
            return terms ? terms.map((term) => ({ label: term.name, value: term.id })) : [];
        }, []);

        const posts = useSelect(
            (select) => {
                const query = {
                    per_page: postsToShow,
                    _embed: true,
                };
                if (category) {
                    query.categories = category;
                }
                return select('core').getEntityRecords('postType', 'post', query);
            },
            [postsToShow, category]
        );

        return (
            <div {...blockProps}>
                <InspectorControls>
                    <PanelBody title="Carousel Settings">
                        <RangeControl
                            label="Number of posts"
                            value={postsToShow}
                            onChange={(value) => setAttributes({ postsToShow: value })}
                            min={1}
                            max={10}
                        />
                        {categories && categories.length > 0 && (
                            <SelectControl
                                label="Filter by Category"
                                value={category}
                                options={[{ label: 'All Categories', value: '' }, ...categories]}
                                onChange={(value) => setAttributes({ category: value })}
                            />
                        )}
                    </PanelBody>
                </InspectorControls>
                <div className="wp-block-twentytwentyfour-child-post-carousel__editor-preview">
                    {posts && posts.length > 0 ? (
                        <Swiper
                            modules={[Navigation, Pagination]}
                            spaceBetween={30}
                            slidesPerView={1}
                            navigation
                            pagination={{ clickable: true }}
                            breakpoints={{
                                768: {
                                    slidesPerView: 2,
                                },
                                1024: {
                                    slidesPerView: 3,
                                },
                            }}
                        >
                            {posts.map((post) => (
                                <SwiperSlide key={post.id}>
                                    <div className="post-carousel-item">
                                        {post._embedded['wp:featuredmedia'] &&
                                            post._embedded['wp:featuredmedia'][0] && (
                                                <img
                                                    src={post._embedded['wp:featuredmedia'][0].source_url}
                                                    alt={post._embedded['wp:featuredmedia'][0].alt_text || post.title.rendered}
                                                />
                                            )}
                                        <h3>{post.title.rendered}</h3>
                                        {post.excerpt.rendered && (
                                            <div dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }} />
                                        )}
                                        <a href={post.link} target="_blank" rel="noopener noreferrer">
                                            Read More
                                        </a>
                                    </div>
                                </SwiperSlide>
                            ))}
                        </Swiper>
                    ) : (
                        <p>No posts found.</p>
                    )}
                </div>
            </div>
        );
    },
    save: () => {
        return null; // Rendered via PHP
    },
});

Note on index.js:

  • This uses JSX, so you’ll need a build process to compile it (e.g., @wordpress/scripts). We’ll set that up next.
  • We’re importing Swiper directly here for the editor preview. The frontend will load Swiper separately.

twentytwentyfour-child/blocks/post-carousel/editor.scss

SCSS

.wp-block-twentytwentyfour-child-post-carousel {
    border: 1px dashed #ccc;
    padding: 15px;

    .wp-block-twentytwentyfour-child-post-carousel__editor-preview {
        .post-carousel-item {
            text-align: center;
            img {
                max-width: 100%;
                height: auto;
                display: block;
                margin-bottom: 10px;
            }
            h3 {
                font-size: 1.2em;
                margin-bottom: 5px;
            }
            p {
                font-size: 0.9em;
                color: #555;
            }
            a {
                display: inline-block;
                margin-top: 10px;
                padding: 5px 10px;
                background-color: #007cba;
                color: #fff;
                text-decoration: none;
                border-radius: 3px;
            }
        }
    }
}

twentytwentyfour-child/blocks/post-carousel/style.scss

SCSS

.wp-block-twentytwentyfour-child-post-carousel {
    .swiper {
        width: 100%;
        height: auto;
    }

    .swiper-slide {
        display: flex;
        justify-content: center;
        align-items: center;
        .post-carousel-item {
            text-align: center;
            padding: 20px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
            background-color: #fff;
            img {
                max-width: 100%;
                height: auto;
                display: block;
                margin: 0 auto 15px auto;
                object-fit: cover; // Ensure images cover the area
                aspect-ratio: 16/9; // Maintain aspect ratio for images
            }
            h3 {
                font-size: 1.5em;
                margin-bottom: 10px;
                color: #333;
            }
            p {
                font-size: 1em;
                line-height: 1.6;
                color: #666;
            }
            a {
                display: inline-block;
                margin-top: 15px;
                padding: 8px 15px;
                background-color: #007cba;
                color: #fff;
                text-decoration: none;
                border-radius: 4px;
                transition: background-color 0.3s ease;

                &:hover {
                    background-color: #005f8f;
                }
            }
        }
    }

    .swiper-button-next,
    .swiper-button-prev {
        color: #007cba;
    }

    .swiper-pagination-bullet-active {
        background: #007cba;
    }
}

twentytwentyfour-child/blocks/post-carousel/render.php

This file handles the frontend rendering of the block.

PHP

<?php
/**
 * Server-side rendering of the post carousel block.
 *
 * @package TwentyTwentyFourChild
 */

$args = array(
    'posts_per_page' => $attributes['postsToShow'],
    'post_status'    => 'publish',
    'orderby'        => 'date',
    'order'          => 'DESC',
);

if ( ! empty( $attributes['category'] ) ) {
    $args['cat'] = (int) $attributes['category'];
}

$posts = new WP_Query( $args );

if ( $posts->have_posts() ) :
    ?>
    <div <?php echo get_block_wrapper_attributes(); ?>>
        <div class="swiper post-carousel-swiper">
            <div class="swiper-wrapper">
                <?php
                while ( $posts->have_posts() ) :
                    $posts->the_post();
                    ?>
                    <div class="swiper-slide">
                        <div class="post-carousel-item">
                            <?php if ( has_post_thumbnail() ) : ?>
                                <img src="<?php echo esc_url( get_the_post_thumbnail_url( get_the_ID(), 'medium' ) ); ?>" alt="<?php echo esc_attr( get_the_title() ); ?>">
                            <?php endif; ?>
                            <h3><?php the_title(); ?></h3>
                            <div class="post-carousel-excerpt">
                                <?php the_excerpt(); ?>
                            </div>
                            <a href="<?php the_permalink(); ?>" class="post-carousel-read-more">Read More</a>
                        </div>
                    </div>
                    <?php
                endwhile;
                ?>
            </div>
            <div class="swiper-pagination"></div>
            <div class="swiper-button-prev"></div>
            <div class="swiper-button-next"></div>
        </div>
    </div>
    <?php
    wp_reset_postdata();
else :
    ?>
    <p <?php echo get_block_wrapper_attributes(); ?>>No posts found.</p>
    <?php
endif;

3. Set up a Build Process for the Block

Gutenberg blocks typically require a build process (like webpack via @wordpress/scripts) to compile JSX, SCSS, and other modern JavaScript features.

  1. Navigate to your child theme’s root directory (twentytwentyfour-child) in your terminal.
  2. Initialize npm: Bashnpm init -y
  3. Install @wordpress/scripts and Swiper: Bashnpm install @wordpress/scripts swiper
  4. Add build scripts to your package.json: Open package.json and add these lines under the scripts section: JSON"scripts": { "build": "wp-scripts build --entry-point=./blocks/post-carousel/index.js --output-path=./blocks/post-carousel/ --output-css=./blocks/post-carousel/style.css --output-editor-css=./blocks/post-carousel/editor.css", "start": "wp-scripts start --entry-point=./blocks/post-carousel/index.js --output-path=./blocks/post-carousel/ --output-css=./blocks/post-carousel/style.css --output-editor-css=./blocks/post-carousel/editor.css" },
    • build: Creates production-ready minified files.
    • start: Watches for changes and rebuilds automatically (useful during development).
  5. Run the build command: Bashnpm run build This will create index.js, editor.css, and style.css (and their minified versions) inside your post-carousel block folder.

4. Enqueue Swiper on the Frontend

We need to enqueue Swiper’s CSS and JS on the frontend only when our block is present.

  1. Open functions.php in your child theme.
  2. Add the following code to enqueue Swiper and initialize the carousel: PHP<?php add_action( 'wp_enqueue_scripts', 'twentytwentyfour_child_enqueue_styles' ); function twentytwentyfour_child_enqueue_styles() { wp_enqueue_style( 'twentytwentyfour-parent-style', get_template_directory_uri() . '/style.css' ); wp_enqueue_style( 'twentytwentyfour-child-style', get_stylesheet_directory_uri() . '/style.css', array('twentytwentyfour-parent-style'), wp_get_theme()->get('Version') ); } add_action( 'init', 'twentytwentyfour_child_register_blocks' ); function twentytwentyfour_child_register_blocks() { register_block_type( get_stylesheet_directory() . '/blocks/post-carousel' ); // Enqueue Swiper on the frontend conditionally add_action( 'wp_enqueue_scripts', 'twentytwentyfour_child_enqueue_swiper_frontend' ); } function twentytwentyfour_child_enqueue_swiper_frontend() { if ( has_block( 'twentytwentyfour-child/post-carousel' ) ) { // Swiper CSS wp_enqueue_style( 'swiper-css', 'https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css', array(), '11' ); // Swiper JS wp_enqueue_script( 'swiper-js', 'https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js', array(), '11', true // In footer ); // Your custom script to initialize Swiper wp_enqueue_script( 'twentytwentyfour-child-post-carousel-init', get_stylesheet_directory_uri() . '/js/post-carousel-init.js', array( 'swiper-js' ), wp_get_theme()->get('Version'), true // In footer ); } }

5. Create the Swiper Initialization Script

Create a js folder in your child theme’s root, and inside it, create post-carousel-init.js.

twentytwentyfour-child/js/post-carousel-init.js

JavaScript

document.addEventListener('DOMContentLoaded', function() {
    // Initialize Swiper on all post carousel blocks
    const carousels = document.querySelectorAll('.wp-block-twentytwentyfour-child-post-carousel .post-carousel-swiper');

    carousels.forEach(carousel => {
        new Swiper(carousel, {
            modules: [Swiper.Navigation, Swiper.Pagination],
            loop: true,
            spaceBetween: 30,
            slidesPerView: 1,
            navigation: {
                nextEl: '.swiper-button-next',
                prevEl: '.swiper-button-prev',
            },
            pagination: {
                el: '.swiper-pagination',
                clickable: true,
            },
            breakpoints: {
                768: {
                    slidesPerView: 2,
                    spaceBetween: 30,
                },
                1024: {
                    slidesPerView: 3,
                    spaceBetween: 30,
                },
            },
        });
    });
});

Summary of File Structure:

twentytwentyfour-child/
├── style.css
├── functions.php
├── js/
│   └── post-carousel-init.js
├── blocks/
│   └── post-carousel/
│       ├── block.json
│       ├── index.js        (Generated by npm build)
│       ├── index.asset.php (Generated by npm build)
│       ├── editor.css      (Generated by npm build)
│       ├── style.css       (Generated by npm build)
│       └── render.php
├── node_modules/           (Created by npm install)
└── package.json            (Created by npm init)

How to Use:

  1. Activate your Twenty Twenty-Four Child theme.
  2. Make sure you have some posts published (with featured images for best results).
  3. Run npm run build in your twentytwentyfour-child directory to compile the block’s assets.
  4. Edit a page or post in your WordPress admin.
  5. Add a new block and search for “Post Carousel”.
  6. Insert the block. You should see a preview of your posts in a carousel format.
  7. Use the block settings in the sidebar to adjust the number of posts and filter by category.
  8. Save/Update the page/post and view it on the frontend to see the working carousel.

This comprehensive guide should get you a fully functional post carousel block for the Twenty Twenty-Four theme!


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *