2 Ways to Create and Display Footer Widgets in WordPress

Here’s our initial static demo that we’re going to convert into a WordPress theme (the colors have been changed for the WordPress version):

Make a Plan

For this demonstration, we’ll create a new simple custom WordPress theme (Playground). Its file structure will look like this:

The theme structureThe theme structureThe theme structure
The theme structure

We’ll host it on a GitHub repo from where you can download it, and then install and activate it on a WordPress site. 

As we already know from the demos, our footer consists of four columns. The first three columns include a heading and a list with links, while the fourth one includes a heading and some social icons with links to the associated channels.

The footer columnsThe footer columnsThe footer columns

The goal is to keep the existing styles but let website admins update the columns’ content. For instance, they might want to remove a link or change the text of a heading. To achieve this, we’ll go through two methods:

  • Define new widget/footer areas using the register_sidebar() built-in function. 
  • Use the free version of the ACF plugin—one of the most popular WordPress plugins—for defining an Options page that will handle the footer content.

Depending on the selected method, the associated code responsible for displaying the footer columns in the front end will sit either on the main-footer-widgets.php or main-footer-acf.php files.

To test it, after the theme activation, navigate to the root URL of your site and make sure that you see something like this. In my case, I’m using WordPress 6.3 which is the latest version at the time of this writing.

The site layoutThe site layoutThe site layout

Of course, the footer columns will be empty. To populate it, use one of the methods described in the next sections.

In the WordPress world, widgets are pieces of information added to different website sections like sidebars, headers, and footers. They live inside a widget area, or as often called a sidebar. Each widget area can have one or more widgets.

Common examples of popular widgets include ones that show post categories, post comments, navigation menus, page links, social links, newsletter forms, etc.

WordPress comes with various predefined widgets, yet you can always add your own by coding them or installing a plugin.

Depending on your theme settings, you can find the registered widget areas and feed them with widgets on the Theme Customizer or Appearance > Widgets in the WordPress Administration Screens.

Footer Widgets Without a Plugin

The register_sidebar() built-in function allows us to create new widget areas. In our case, we need four columns, so we’ll call this function four times. But when will we call it? Actually, when all default WordPress widgets have been registered. That’s what the widgets_init hook is responsible for.

Here’s the code that we need to throw in the functions.php file:

1
function playground_sidebar_registration() {
2
    $shared_args = array(
3
        'before_title'  => '<h5 class="widget-title">',
4
        'after_title'   => '</h5>',
5
        'before_widget' => '<div id="%1$s" class="widget %2$s">',
6
        'after_widget'  => '</div>',
7
    );
8
    
9
    register_sidebar(
10
        array_merge(
11
            $shared_args,
12
            array(
13
                'name'        => __( 'Footer 1', 'playground' ),
14
                'id'          => 'footer-1',
15
                'description' => __( 'Add widgets here.', 'playground' ),
16
            )
17
        )
18
    );
19
    
20
    register_sidebar(
21
        array_merge(
22
            $shared_args,
23
            array(
24
                'name'        => __( 'Footer 2', 'playground' ),
25
                'id'          => 'footer-2',
26
                'description' => __( 'Add widgets here.', 'playground' ),
27
            )
28
        )
29
    );
30
    
31
    register_sidebar(
32
        array_merge(
33
            $shared_args,
34
            array(
35
                'name'        => __( 'Footer 3', 'playground' ),
36
                'id'          => 'footer-3',
37
                'description' => __( 'Add widgets here.', 'playground' ),
38
            )
39
        )
40
    );
41
    
42
    register_sidebar(
43
        array_merge(
44
            $shared_args,
45
            array(
46
                'name'        => __( 'Footer 4', 'playground' ),
47
                'id'          => 'footer-4',
48
                'description' => __( 'Add widgets here.', 'playground' ),
49
            )
50
        )
51
    );
52
}
53
add_action( 'widgets_init', 'playground_sidebar_registration' );

The widget title we added through the code will only be available on the legacy widgets (see next section).

After doing this, we’ll see the newly added widget areas inside the Widgets admin page.

From here, we can add blocks just like we would in the Post Editor.

Blocks to add to the widgetsBlocks to add to the widgetsBlocks to add to the widgets

In the first three widget areas, we’ll add two text blocks: a Heading and a List.

The blocks added in the first three widget areasThe blocks added in the first three widget areasThe blocks added in the first three widget areas

In the fourth widget area, we’ll add a Custom HTML block where we’ll insert the code for the socials.

The blocks added in the fourth widget areaThe blocks added in the fourth widget areaThe blocks added in the fourth widget area

An alternative implementation, instead of using the Custom HTML block, would be to have the social links stored in a Theme Options Page and generate the above markup using a shortcode. Anyhow, chances are that the social links will appear in other website parts like header and contact pages.

Then, to show the widgets in the front end, we’ll use the dynamic_sidebar() function and pass as a parameter the ID of the associated registered widget area. Also, before printing the widgets in the front end, we’ll use the is_active_sidebar() function to check whether the widget areas contain any of them.

Here’s the complete code added inside the main-footer-widgets.php file:

1
<footer class="site-footer">
2
    <div class="container">
3
        <div class="logo-wrapper">
4
            <img width="178" height="38" src="https://assets.codepen.io/162656/forecastr-logo-white.svg" alt="forecastr logo">
5
            <div>...for creating memories!</div>
6
        </div>
7
        
8
        <div class="widgets-wrapper">
9
            <?php if ( is_active_sidebar( 'footer-1' ) ) : ?>
10
                <div class="widget">
11
                    <?php dynamic_sidebar( 'footer-1' ); ?>
12
                </div>
13
                <?php
14
            endif;
15
            
16
            if ( is_active_sidebar( 'footer-2' ) ) :
17
                ?>
18
                <div class="widget">
19
                    <?php dynamic_sidebar( 'footer-2' ); ?>
20
                </div>
21
                <?php
22
            endif;
23
            
24
            if ( is_active_sidebar( 'footer-3' ) ) :
25
                ?>
26
                <div class="widget">
27
                    <?php dynamic_sidebar( 'footer-3' ); ?>
28
                </div>
29
                <?php
30
            endif;
31
            
32
            if ( is_active_sidebar( 'footer-4' ) ) :
33
                ?>
34
                <div class="widget">
35
                    <?php dynamic_sidebar( 'footer-4' ); ?>
36
                </div>
37
            <?php endif; ?>
38
        </div>
39
    </div>
40
</footer>

Remove the Widgets Block Editor

WordPress 5.8 replaced classic widgets with Gutenberg block-based ones. Still, there are plenty of options in case we want to disable the Widgets Block Editor and create the widgets using the Classic Widgets Editor. One approach is to add the following code to the functions.php file or install the Classis Widgets plugin.

1
function playground_setup() {
2
    remove_theme_support( 'widgets-block-editor' );
3
}
4
add_action( 'after_setup_theme', 'playground_setup' );

Upon doing so, we’ll have available the classic Text widget for populating the widget areas.

The classic Text widgetThe classic Text widgetThe classic Text widget

For more info regarding the compatibility of the classic and block-based widgets, consider the official documentation.

Footer Widgets With ACF

There are many plugins available for setting up footer widgets. One of my favorites is the Advanced Custom Fields (ACF), which provides total control of the output markup. The primary use of it is the creation of custom fields, post types, and taxonomies. One of the nice add-ons of its premium version (ACF PRO) is the Options Page. This works as the primary Theme Options Page, where we specify generic settings that should be available on any page like social links, banners, APIs, etc. 

It’s worth mentioning that at the time of this writing, I’m using the plugin’s latest version (6.2.0).

In our case, we won’t have this built-in page as we’ll install the plugin’s free version. However, we can construct it somehow. That said, let’s create a custom page template (Options Page) inside the page-templates folder. 

Our custom page templateOur custom page templateOur custom page template

Then, we’ll define a new Field Group (Options Page Fields) that will appear only on the Options Page. Inside this group, we’ll declare as widgets:

  • Four Wysiwyg fields and
  • one Group field for the socials. This field will consist of four URL fields for storing the social links.
The footer fieldsThe footer fieldsThe footer fields
The social links fieldsThe social links fieldsThe social links fields

Now, if we go to the Options Page, we’ll see these new fields and be able to add content to them.

The new ACF fieldsThe new ACF fieldsThe new ACF fields
The new ACF fieldsThe new ACF fieldsThe new ACF fields

To display the contents of the Options Page, we’ll first have to grab its ID. The query below will do this job:

1
$options_page_id = get_posts(
2
    array(
3
		'post_type'      => 'page',
4
		'posts_per_page' => 1,
5
		'fields'         => 'ids',
6
		'meta_key'       => '_wp_page_template',
7
		'meta_value'     => 'page-templates/options.php',
8
	)
9
)[0];

It’s wiser to avoid hardcoding the page ID in the code as, at some point, we might use another page as an Options Page or want to translate it into other languages. 

Then, using ACF’s get_field() function, we’ll grab the fields of the four Wysiwyg fields and output them after making the essential checks.

Here’s the necessary code added inside the main-footer-acf.php file:

1
<?php 
2
$footer1 = get_field( 'footer_1', $options_page_id );
3
$footer2 = get_field( 'footer_2', $options_page_id );
4
$footer3 = get_field( 'footer_3', $options_page_id );
5
$footer4 = get_field( 'footer_4', $options_page_id );
6
?>
7

8
<footer class="site-footer">
9
    <div class="container">
10
        <div class="logo-wrapper">
11
            <img width="178" height="38" src="https://assets.codepen.io/162656/forecastr-logo-white.svg" alt="forecastr logo">
12
            <div>...for creating memories!</div>
13
        </div>
14
        
15
        <div class="widgets-wrapper">
16
            <?php if ( $footer1 ) : ?>
17
                <div class="widget">
18
                    <?php echo wp_kses_post( $footer1 ); ?>
19
                </div>
20
                <?php
21
            endif;
22
            
23
            if ( $footer2 ) :
24
                ?>
25
                <div class="widget">
26
                    <?php echo wp_kses_post( $footer2 ); ?>
27
                </div>
28
                <?php
29
            endif;
30
            
31
            if ( $footer3 ) :
32
                ?>
33
                <div class="widget">
34
                    <?php echo wp_kses_post( $footer3 ); ?>
35
                </div>
36
            <?php endif; ?>
37
            
38
            <div class="widget">
39
                <?php
40
                if ( $footer4 ) :
41
                    echo wp_kses_post( $footer4 );
42
                endif;
43
                // SOCIALS HERE
44
                ?>
45
            </div>
46
        </div>
47
    </div>
48
</footer>

Moving on, inside the fourth column, we’ll also add the code for displaying the socials. In this case, we’ll add some checks as some social accounts might not be available.

Here’s the code:

1
<?php 
2
$socials         = get_field( 'socials', $options_page_id );
3
$socials_fb      = $socials['facebook'];
4
$socials_in      = $socials['instagram'];
5
$socials_link    = $socials['linkedin'];
6
$socials_yt      = $socials['youtube'];
7

8
<ul class="socials">
9
    <?php if ( $socials_fb ) : ?>
10
        <li>
11
            <a href="<?php echo esc_url( $socials_fb ); ?>" aria-label="Find us on Facebook" target="_blank">
12
                <svg xmlns="https://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" aria-hidden="true">
13
                    <path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm3 8h-1.35c-.538 0-.65.221-.65.778v1.222h2l-.209 2h-1.791v7h-3v-7h-2v-2h2v-2.308c0-1.769.931-2.692 3.029-2.692h1.971v3z" />
14
                </svg>
15
            </a>
16
        </li>
17
        <?php
18
    endif;
19
    if ( $socials_in ) :
20
        ?>
21
        <li>
22
            <a href="<?php echo esc_url( $socials_in ); ?>" aria-label="Find us on Instagram" target="_blank">
23
                <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" aria-hidden="true">
24
                    <path d="M14.829 6.302c-.738-.034-.96-.04-2.829-.04s-2.09.007-2.828.04c-1.899.087-2.783.986-2.87 2.87-.033.738-.041.959-.041 2.828s.008 2.09.041 2.829c.087 1.879.967 2.783 2.87 2.87.737.033.959.041 2.828.041 1.87 0 2.091-.007 2.829-.041 1.899-.086 2.782-.988 2.87-2.87.033-.738.04-.96.04-2.829s-.007-2.09-.04-2.828c-.088-1.883-.973-2.783-2.87-2.87zm-2.829 9.293c-1.985 0-3.595-1.609-3.595-3.595 0-1.985 1.61-3.594 3.595-3.594s3.595 1.609 3.595 3.594c0 1.985-1.61 3.595-3.595 3.595zm3.737-6.491c-.464 0-.84-.376-.84-.84 0-.464.376-.84.84-.84.464 0 .84.376.84.84 0 .463-.376.84-.84.84zm-1.404 2.896c0 1.289-1.045 2.333-2.333 2.333s-2.333-1.044-2.333-2.333c0-1.289 1.045-2.333 2.333-2.333s2.333 1.044 2.333 2.333zm-2.333-12c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm6.958 14.886c-.115 2.545-1.532 3.955-4.071 4.072-.747.034-.986.042-2.887.042s-2.139-.008-2.886-.042c-2.544-.117-3.955-1.529-4.072-4.072-.034-.746-.042-.985-.042-2.886 0-1.901.008-2.139.042-2.886.117-2.544 1.529-3.955 4.072-4.071.747-.035.985-.043 2.886-.043s2.14.008 2.887.043c2.545.117 3.957 1.532 4.071 4.071.034.747.042.985.042 2.886 0 1.901-.008 2.14-.042 2.886z" />
25
                </svg>
26
            </a>
27
        </li>
28
        <?php
29
    endif;
30
    if ( $socials_link ) :
31
        ?>
32
        <li>
33
            <a href="<?php echo esc_url( $socials_link ); ?>" aria-label="Find us on LinkedIn" target="_blank">
34
                <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" aria-hidden="true">
35
                    <path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-2 16h-2v-6h2v6zm-1-6.891c-.607 0-1.1-.496-1.1-1.109 0-.612.492-1.109 1.1-1.109s1.1.497 1.1 1.109c0 .613-.493 1.109-1.1 1.109zm8 6.891h-1.998v-2.861c0-1.881-2.002-1.722-2.002 0v2.861h-2v-6h2v1.093c.872-1.616 4-1.736 4 1.548v3.359z" />
36
                </svg>
37
            </a>
38
        </li>
39
        <?php
40
    endif;
41
    if ( $socials_yt ) :
42
        ?>
43
        <li>
44
            <a href="<?php echo esc_url( $socials_yt ); ?>" aria-label="Find us on YouTube" target="_blank">
45
                <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" aria-hidden="true">
46
                    <path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm4.441 16.892c-2.102.144-6.784.144-8.883 0-2.276-.156-2.541-1.27-2.558-4.892.017-3.629.285-4.736 2.558-4.892 2.099-.144 6.782-.144 8.883 0 2.277.156 2.541 1.27 2.559 4.892-.018 3.629-.285 4.736-2.559 4.892zm-6.441-7.234l4.917 2.338-4.917 2.346v-4.684z" />
47
                </svg>
48
            </a>
49
        </li>
50
    <?php endif; ?>
51
</ul>

As discussed earlier, an alternative social implementation, is to build a shortcode and pass it to the fourth widget.

An alternative approach, instead of creating a custom page template, would be to create a new section in the WordPress Customizer for the Theme Options and include there a setting for targeting the Options Page.
Add a new section in the WordPress CustomizerAdd a new section in the WordPress CustomizerAdd a new section in the WordPress Customizer
Add a new setting inside the new sectionAdd a new setting inside the new sectionAdd a new setting inside the new section