Build classic and block WordPress themes from scratch: the template hierarchy, The Loop, the_title() and the_content(), template parts, menus, the Customizer, child themes and theme.json.
Lessons
A widget area — still called a "sidebar" in code even when it sits in a footer — is a slot you register with register_sidebar() and print with dynamic_sidebar(), which the site owner fills with blocks or widgets from Appearance → Widgets.
<?php
function gdc_starter_widgets_init() {
register_sidebar(
array(
'name' => esc_html__( 'Blog Sidebar', 'gdc-starter' ),
'id' => 'sidebar-1',
'description' => esc_html__( 'Shown beside blog posts.', 'gdc-starter' ),
'before_widget' => '<section id="%1$s" class="widget %2$s">',
'after_widget' => '</section>',
'before_title' => '<h2 class="widget-title">',
'after_title' => '</h2>',
)
);
// Three footer columns in one loop.
for ( $i = 1; $i <= 3; $i++ ) {
register_sidebar(
array(
/* translators: %d: footer column number */
'name' => sprintf( esc_html__( 'Footer Column %d', 'gdc-starter' ), $i ),
'id' => 'footer-' . $i,
'before_widget' => '<div id="%1$s" class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
)
);
}
}
add_action( 'widgets_init', 'gdc_starter_widgets_init' );In before_widget, WordPress replaces %1$s with the widget's unique ID and %2$s with its class name, so every widget can be targeted in CSS. The id you choose — sidebar-1 — is what you print later. Keep it lowercase and never change it once the theme is in use, or the owner's widgets disappear.
<?php
/**
* The sidebar containing the main widget area.
*/
if ( ! is_active_sidebar( 'sidebar-1' ) ) {
return;
}
?>
<aside id="secondary" class="widget-area" aria-label="<?php esc_attr_e( 'Sidebar', 'gdc-starter' ); ?>">
<?php dynamic_sidebar( 'sidebar-1' ); ?>
</aside>The early return is the important line. is_active_sidebar() is false when the area has no widgets, so the empty <aside> is never printed and your layout can switch to full width.
<?php get_header(); ?>
<div class="layout <?php echo is_active_sidebar( 'sidebar-1' ) ? 'has-sidebar' : 'no-sidebar'; ?>">
<main class="site-main">
<?php /* The Loop */ ?>
</main>
<?php get_sidebar(); ?>
</div>
<?php get_footer(); ?>get_sidebar() loads sidebar.php. get_sidebar( 'shop' ) would load sidebar-shop.php.
Since WordPress 5.8, Appearance → Widgets uses the block editor, so owners can place any block — a paragraph, an image, a list of latest posts — in your widget area. Your register_sidebar() code is unchanged. Do test with blocks as well as classic widgets: blocks do not use before_title, so a heading block appears exactly as the owner set it.
sidebar-1 and three footer columns.sidebar.php with the is_active_sidebar() guard and include it in single.php.<aside> in the source.In accordance with Section 6 & Section 7 of the DPDP Act 2023, third-party analytics and marketing trackers are strictly gated behind your affirmative consent. You may withdraw consent at any time under Section 6(4).
Essential for session authentication (ci_session), Cross-Site Request Forgery security (csrf_cookie_name), and Cloudflare DDoS/bot prevention. Cannot be disabled.
Gated under DPDP Section 7. Allows Google Analytics 4 (Google Consent Mode v2) to measure aggregate, anonymized tool usage.
Enables browser local storage to preserve your theme selection (dark/light mode) and code editor layout between visits.
Used to display developer sponsorships and native tool partner notices.