The WordPress Theme Index Template

Index.php is the most crucial WordPress Theme Template. Not only because WordPress needs to use it if you’re missing any of its brother and sister templates (like, archive.php or tag.php) but because the work we do here, getting this template right, will help us breeze through the rest of our templates (with the exception of the dreaded comments template; that’s just plain difficult no matter how you look at it).

Please note: This lesson assumes that you have already added the basic HTML structural elements to your index.php file, which we covered in WordPress Theme Template and Directory Structure. If your index.php file is empty, please work through that lesson first, and then come right back.

Calling in the Header

Open up the index.php. We added some basic structural HTML in WordPress Theme Template and Directory Structure. Here’s what index.php looked like at the end of that lesson.

<div id="primary" class="content-area">
    <div id="content" class="site-content">
    </div><!-- #content .site-content -->
</div><!-- #primary .content-area -->

Paste the following code at the top of the file, before everything else.

<?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.
 * Learn more: http://codex.wordpress.org/Template_Hierarchy
 *
 * @package Shape
 * @since Shape 1.0
 */

get_header(); ?>

It’s our usual documentation information, followed by the get_header() function call at the end. This function tells WordPress to include the header.php file.

The Loop

Even though it’s stuck right in the middle of your template, in a metaphorical sense, index.php begins and ends with The Loop. Without it you don’t have anything. Here’s what it looks like.

<?php while ( have_posts() ) : the_post() ?>
<?php endwhile; ?>

Simple really. And not even deceptively simple. While you’ve got posts in your database your theme will loop through them and for each one, do something. Getting the “do something” just right is the tricky part. But even that can be simple.

Try out this loop to get started and we’ll work on building it up. Put the following code inside your .site-content div in index.php.

<?php /* Start the Loop */ ?>
<?php while ( have_posts() ) : the_post(); ?>
<?php the_content(); ?>
<?php endwhile; ?>

Here’s what your .site-content div should look like:

<div id="content" class="site-content" role="main">
<?php /* Start the Loop */ ?>
<?php while ( have_posts() ) : the_post(); ?>
     <?php the_content(); ?>
<?php endwhile; ?>
</div><!-- #content .site-content -->

What do you get if you do that? All the post content in a big smushed up pile. But it could be different.

<?php while ( have_posts() ) : the_post() ?>
<div class="entry-summary">
     <?php the_excerpt(); ?>
</div><!-- .entry-summary -->
<?php endwhile; ?>

Do you see what we just did there? Now you’ve got a loop that lists post excerpts inside divs, each with a class of .entry-summary. (Plus, now you can see what the_content() and the_excerpt() do).

Basically, you make a loop (starts with while ends with endwhile) and put some stuff in it—stuff being WordPress Template Tags that pull information out of the posts we’re looping though, just like bloginfo() pulled information out of our WordPress settings in the last lesson.

Alright, let’s make a really awesome loop. Let’s start with our basic, smushed up one. But we’ll make sure it’s ready for the More Tag and the Next Page Tag. We’ll also put it in its own div and let machines know it’s the content of a blog post with the microformat class “entry-content”. Finally, we want to display excerpts on search result pages, and the full content on all other pages. Here’s what it looks like. No need to paste anything yet, just observe.

<?php if ( is_search() ) : // Only display Excerpts for Search ?>
<div class="entry-summary">
     <?php the_excerpt(); ?>
</div><!-- .entry-summary -->
<?php else : ?>
<div class="entry-content">
     <?php the_content( __( 'Continue reading <span class="meta-nav">→</span>', 'shape' ) ); ?>
     <?php wp_link_pages( array( 'before' => '<div class="page-links">' . __( 'Pages:', 'shape' ), 'after' => '</div>' ) ); ?>
</div><!-- .entry-content -->
<?php endif; ?>

How about the post title? That’s pretty simple too. We’ll use the Template Tag the_title() to get the title of the post and wrap it in an <a> tag that links to the_permalink() (that’s the permanent link to any particular post). We’ll also add in a title attribute and another microformat (bookmark) that tells machines (like Google) that this is the permalink to a blog post. Here’s how our code looks so far, with the title added. Again, hold off on pasting anything for now.

<header class="entry-header">
     <h1 class="entry-title"><a href="<?php the_permalink(); ?>" title="<?php echo esc_attr( sprintf( __( 'Permalink to %s', '_s' ), the_title_attribute( 'echo=0' ) ) ); ?>" rel="bookmark"><?php the_title(); ?></a></h1>
</header><!-- .entry-header -->

<?php if ( is_search() ) : // Only display Excerpts for Search ?>
<div class="entry-summary">
     <?php the_excerpt(); ?>
</div><!-- .entry-summary -->
<?php else : ?>
<div class="entry-content">
     <?php the_content( __( 'Continue reading <span class="meta-nav">→</span>', 'shape' ) ); ?>
     <?php wp_link_pages( array( 'before' => '<div class="page-links">' . __( 'Pages:', 'shape' ), 'after' => '</div>' ) ); ?>
</div><!-- .entry-content -->
<?php endif; ?>

Now for all the extra bits that attend to any blog post: who wrote it, the time it was published, categories, tags, comments links. I like to break all this up into two sections: the meta stuff (author and entry date) which I put before the post content, and the utility stuff (categories, tags and comments link) that I put after the content. We’ll also put the post in its own containing article element with the title.

Let’s take a look at the whole loop together. I’ve inserted some PHP comments in here to help guide you along. Still no need to paste anything yet (we’ll do so very shortly, I promise). Just watch, and learn.

<?php /* The Loop — with comments! */ ?>
<?php while ( have_posts() ) : the_post() ?>
<?php /* Create an HTML5 article section with a unique ID thanks to the_ID() and semantic classes with post_class() */ ?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
     <header class="entry-header">
          <h1 class="entry-title"><a href="<?php the_permalink(); ?>" title="<?php echo esc_attr( sprintf( __( 'Permalink to %s', '_s' ), the_title_attribute( 'echo=0' ) ) ); ?>" rel="bookmark"><?php the_title(); ?></a></h1>

          <?php if ( 'post' == get_post_type() ) : // Only display post date and author if this is a Post, not a Page. ?>
          <div class="entry-meta">
               <?php shape_posted_on(); ?>
          </div><!-- .entry-meta -->
          <?php endif; ?>
     </header><!-- .entry-header -->

     <?php if ( is_search() ) : // Only display Excerpts on Search results pages ?>
     <div class="entry-summary">
          <?php the_excerpt(); ?>
     </div><!-- .entry-summary -->
     <?php else : ?>
     <div class="entry-content">
          <?php the_content( __( 'Continue reading <span class="meta-nav">→</span>', 'shape' ) ); ?>
          <?php wp_link_pages( array( 'before' => '<div class="page-links">' . __( 'Pages:', 'shape' ), 'after' => '</div>' ) ); ?>
     </div><!-- .entry-content -->
     <?php endif; ?>

<?php /* Show the post's tags, categories, and a comment link. */ ?>
     <footer class="entry-meta">
          <?php if ( 'post' == get_post_type() ) : // Hide category and tag text for Pages in Search results ?>
          <?php
               /* translators: used between list items, there is a space after the comma */
               $categories_list = get_the_category_list( __( ', ', 'shape' ) );
               if ( $categories_list && shape_categorized_blog() ) :
          ?>
          <span class="cat-links">
               <?php printf( __( 'Posted in %1$s', 'shape' ), $categories_list ); ?>
          </span>
          <?php endif; // End if categories ?>

          <?php
               /* translators: used between list items, there is a space after the comma */
               $tags_list = get_the_tag_list( '', __( ', ', 'shape' ) );
               if ( $tags_list ) :
          ?>
               <span class="sep"> | </span>
               <span class="tag-links">
                    <?php printf( __( 'Tagged %1$s', 'shape' ), $tags_list ); ?>
               </span>
               <?php endif; // End if $tags_list ?>
          <?php endif; // End if 'post' == get_post_type() ?>

          <?php if ( ! post_password_required() && ( comments_open() || '0' != get_comments_number() ) ) : ?>
          <span class="sep"> | </span>
          <span class="comments-link"><?php comments_popup_link( __( 'Leave a comment', 'shape' ), __( '1 Comment', 'shape' ), __( '% Comments', 'shape' ) ); ?></span>
          <?php endif; ?>

          <?php edit_post_link( __( 'Edit', 'shape' ), '<span class="sep"> |   </span><span class="edit-link">', '</span>' ); ?>
     </footer><!-- .entry-meta -->
<?php /* Close up the article and end the loop. */ ?>
</article><!-- #post-<?php the_ID(); ?> -->
<?php endwhile; ?>

To display the meta stuff (author and post date), we’re calling a function called shape_posted_on() (line 10 above). We’ll create that function a little bit later.

For now, let’s talk more about the utility stuff in the code block (categories and tags), because it’s… complicated. Here I think you’ll see the benefit of getting something right once and standing on the shoulders of others. It’s complicated because we need to account for a few different scenarios: the existence of only one category and multiple tags; one category and no tags; multiple categories and multiple tags; or multiple categories and no tags. Also, we have to hide the category and tag links for Pages on Search results pages. Finally, we want to display a comments link only if comments are open or if there is at least one comment. We also want to print a link to our permalink here for bookmarking purposes and the edit post link at the end for site administrators.

And that means … what looks like a mess of IF statements, as you can see if you look back up at lines 26 through 57 in the previous code example. It can be daunting. The code is commented but remember to look for the blocks of IF and ELSEIF statements and you’ll be fine.

Adding metadata functions
Now, let’s create the shape_posted_on() function. We’ll also add shape_categorized_blog() (line 32 above), a function we use to check if the blog has more than 1 category.

Since these are functions we can reuse, we’ll put them in template-tags.php. Remember that file, from Setting Up Your Theme Functions? Open it up, and add:

if ( ! function_exists( 'shape_posted_on' ) ) :
/**
 * Prints HTML with meta information for the current post-date/time and author.
 *
 * @since Shape 1.0
 */
function shape_posted_on() {
	printf( __( 'Posted on <a href="%1$s" title="%2$s" rel="bookmark"><time class="entry-date" datetime="%3$s" pubdate>%4$s</time></a><span class="byline"> by <span class="author vcard"><a class="url fn n" href="%5$s" title="%6$s" rel="author">%7$s</a></span></span>', 'shape' ),
		esc_url( get_permalink() ),
		esc_attr( get_the_time() ),
		esc_attr( get_the_date( 'c' ) ),
		esc_html( get_the_date() ),
		esc_url( get_author_posts_url( get_the_author_meta( 'ID' ) ) ),
		esc_attr( sprintf( __( 'View all posts by %s', 'shape' ), get_the_author() ) ),
		esc_html( get_the_author() )
	);
}
endif;

/**
 * Returns true if a blog has more than 1 category
 *
 * @since Shape 1.0
 */
function shape_categorized_blog() {
	if ( false === ( $all_the_cool_cats = get_transient( 'all_the_cool_cats' ) ) ) {
		// Create an array of all the categories that are attached to posts
		$all_the_cool_cats = get_categories( array(
			'hide_empty' => 1,
		) );

		// Count the number of categories that are attached to the posts
		$all_the_cool_cats = count( $all_the_cool_cats );

		set_transient( 'all_the_cool_cats', $all_the_cool_cats );
	}

	if ( '1' != $all_the_cool_cats ) {
		// This blog has more than 1 category so shape_categorized_blog should return true
		return true;
	} else {
		// This blog has only 1 category so shape_categorized_blog should return false
		return false;
	}
}

/**
 * Flush out the transients used in shape_categorized_blog
 *
 * @since Shape 1.0
 */
function shape_category_transient_flusher() {
	// Like, beat it. Dig?
	delete_transient( 'all_the_cool_cats' );
}
add_action( 'edit_category', 'shape_category_transient_flusher' );
add_action( 'save_post', 'shape_category_transient_flusher' );

In shape_posted_on(), we’re using a bunch of template tags to grab the post’s date, time, author, author archive URL, and permalink.

In shape_categorized_blog(), we’re retrieving all of the categories that have at least one post, and storing them into a temporary variable (known as a “transient”) — $all_the_cool_cats — in array format. If there is more than one category, categories will show up in the post utility links (e.g. “Filed under: Category Name”).

In the last function, shape_category_transient_flusher(), we delete the temporary variable, $all_the_cool_cats, whenever we 1) edit a category; or 2) save a post. Why? Because that’s when you might add new categories. By deleting $all_the_cool_cats, you force it to constantly update itself, so that it stays current with the number of categories you have on your blog.

Simplifying Our Index Template

Now we’ve got the Loop in place. It’s shaping up! Now, let’s make this even better. Since we’re going to use the code inside the Loop in multiple templates, wouldn’t it be nice if we could write this code once and call it up whenever we need it, instead of replicating it across template files?

Wouldn’t be nice, also, if we had a clean way to change the presentation of posts for each Post Format that our theme supports?

Luckily for us, there’s a nifty WordPress function, get_template_part(), that we can use to accomplish all of the above.

According to the WordPress Codex, get_template_part() lets you:

load a template part into a template (other than header, sidebar, footer). Makes it easy for a theme to reuse sections of code and an easy way for child themes to replace sections of their parent theme.

Since we’re going to reuse the code inside our Loop, let’s place this code into a “template part” that we can load into the theme via get_template_part() whenever we need it.

This is why I told you to hold off on pasting any of the Loop code into index.php. But now it’s time to get a-pastin’. Open content.php, and add the following code (it’s the same as the previous code example, with some documentation added at the top).

<?php
/**
 * @package Shape
 * @since Shape 1.0
 */
?>

<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
	<header class="entry-header">
		<h1 class="entry-title"><a href="<?php the_permalink(); ?>" title="<?php echo esc_attr( sprintf( __( 'Permalink to %s', 'shape' ), the_title_attribute( 'echo=0' ) ) ); ?>" rel="bookmark"><?php the_title(); ?></a></h1>

		<?php if ( 'post' == get_post_type() ) : ?>
		<div class="entry-meta">
			<?php shape_posted_on(); ?>
		</div><!-- .entry-meta -->
		<?php endif; ?>
	</header><!-- .entry-header -->

	<?php if ( is_search() ) : // Only display Excerpts for Search ?>
	<div class="entry-summary">
		<?php the_excerpt(); ?>
	</div><!-- .entry-summary -->
	<?php else : ?>
	<div class="entry-content">
		<?php the_content( __( 'Continue reading <span class="meta-nav">&rarr;</span>', 'shape' ) ); ?>
		<?php wp_link_pages( array( 'before' => '<div class="page-links">' . __( 'Pages:', 'shape' ), 'after' => '</div>' ) ); ?>
	</div><!-- .entry-content -->
	<?php endif; ?>

	<footer class="entry-meta">
		<?php if ( 'post' == get_post_type() ) : // Hide category and tag text for pages on Search ?>
			<?php
				/* translators: used between list items, there is a space after the comma */
				$categories_list = get_the_category_list( __( ', ', 'shape' ) );
				if ( $categories_list && shape_categorized_blog() ) :
			?>
			<span class="cat-links">
				<?php printf( __( 'Posted in %1$s', 'shape' ), $categories_list ); ?>
			</span>
			<?php endif; // End if categories ?>

			<?php
				/* translators: used between list items, there is a space after the comma */
				$tags_list = get_the_tag_list( '', __( ', ', 'shape' ) );
				if ( $tags_list ) :
			?>
			<span class="sep"> | </span>
			<span class="tag-links">
				<?php printf( __( 'Tagged %1$s', 'shape' ), $tags_list ); ?>
			</span>
			<?php endif; // End if $tags_list ?>
		<?php endif; // End if 'post' == get_post_type() ?>

		<?php if ( ! post_password_required() && ( comments_open() || '0' != get_comments_number() ) ) : ?>
		<span class="sep"> | </span>
		<span class="comments-link"><?php comments_popup_link( __( 'Leave a comment', 'shape' ), __( '1 Comment', 'shape' ), __( '% Comments', 'shape' ) ); ?></span>
		<?php endif; ?>

		<?php edit_post_link( __( 'Edit', 'shape' ), '<span class="sep"> | </span><span class="edit-link">', '</span>' ); ?>
	</footer><!-- .entry-meta -->
</article><!-- #post-<?php the_ID(); ?> -->

Now, return to index.php, and remove all of the code that’s between <div id="content" class="site-content"> and </div><!-- #content .site-content -->. Replace it with the following.

<?php if ( have_posts() ) : ?>
     <?php /* Start the Loop */ ?>
     <?php while ( have_posts() ) : the_post(); ?>

          <?php
          /* Include the Post-Format-specific template for the content.
          * If you want to overload this in a child theme then include a file
          * called content-___.php (where ___ is the Post Format name) and    that will be used instead.
          */
          get_template_part( 'content', get_post_format() );
          ?>
     <?php endwhile; ?>
<?php endif; ?>

Do you see what we’re doing here? In plain English, this means: “Fill this Loop with the code inside content.php by default. But first, check the Post Format for this post. Search the theme files for a Post-Format-Specific template, such as content-aside.php, or content-quote.php. If you find one, load that template for this post instead. Otherwise, load content.php.”

And there we have it. A clean structure for our Loop that we can reuse multiple times. Plus, we can now create a different layout for our post formats.

The Aside Post Format

We added support for the Aside post format earlier. So let’s do something with it. How you display posts with each format is up to you, but the guidelines on the Codex are a good starting point.

According to the guidelines, Aside posts are intended to be short and sweet, and they’re typically formatted without a title. Let’s do that. Let’s also hide the author name, and categories and tags.

Back in WordPress Theme Template & Directory Structure, we created content-aside.php, a Post-Format-specific template just for Aside posts. Open it now, and paste the following.

<?php
/**
 * The template for displaying posts in the Aside post format
 * @package Shape
 * @since Shape 1.0
 */
?>

<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
	<header class="entry-header">
		<h1 class="entry-title"><a href="<?php the_permalink(); ?>" title="<?php echo esc_attr( sprintf( __( 'Permalink to %s', 'shape' ), the_title_attribute( 'echo=0' ) ) ); ?>" rel="bookmark"><?php the_title(); ?></a></h1>
	</header><!-- .entry-header -->

	<?php if ( is_search() ) : // Only display Excerpts for Search ?>
	<div class="entry-summary">
		<?php the_excerpt(); ?>
	</div><!-- .entry-summary -->
	<?php else : ?>
	<div class="entry-content">
		<?php the_content( __( 'Continue reading <span class="meta-nav">&rarr;</span>', 'shape' ) ); ?>
		<?php wp_link_pages( array( 'before' => '<div class="page-links">' . __( 'Pages:', 'shape' ), 'after' => '</div>' ) ); ?>
	</div><!-- .entry-content -->
	<?php endif; ?>

	<footer class="entry-meta">
		<a href="<?php the_permalink(); ?>" title="<?php echo esc_attr( sprintf( __( 'Permalink to %s', 'shape' ), the_title_attribute( 'echo=0' ) ) ); ?>" rel="bookmark"><?php echo get_the_date(); ?></a>
		<?php if ( ! post_password_required() && ( comments_open() || '0' != get_comments_number() ) ) : ?>
		<span class="sep"> | </span>
		<span class="comments-link"><?php comments_popup_link( __( 'Leave a comment', 'shape' ), __( '1 Comment', 'shape' ), __( '% Comments', 'shape' ) ); ?></span>
		<?php endif; ?>

		<?php edit_post_link( __( 'Edit', 'shape' ), '<span class="sep"> | </span><span class="edit-link">', '</span>' ); ?>
	</footer><!-- .entry-meta -->
</article><!-- #post-<?php the_ID(); ?> -->

You’ll see that it’s nearly identical to content.php, minus the title, author name, categories, and tags.

And there you have it. A Post-Format-Specific template!

Navigation

Now we need a way to navigate back through posts — both on single posts and on archive pages.

For archive pages, we’ll do this with 2 WordPress Template Tags: next_posts_link() and previous_posts_link(). These 2 functions … they don’t do what you think they do. I think the WordPress codex says it best.

next posts link
This creates a link to the previous posts. Yes, it says “next posts,” but it’s named that just to confuse you.
previous posts link
This creates a link to the next posts. Yes, it says “previous posts,” but it’s named that just to confuse you.

Just like everything in index.php, post navigation needs to be given some careful thought when we’re building it for the first time because we’re going to wind up using it on almost every page in our blog.

I like to have post navigation above and below the content. Depending on how you use this code in any particular situation, you may not use it though. No matter, we can always hide it like so.

.single #nav-above {
    display:none;
}

That CSS will hide post navigation above the content on single posts.

Just like we did for the loop, we’re going to place the code for our navigation inside a multipurpose function that can output navigation for both single posts and archive pages. We’ll call the function in our templates wherever we want to show navigation. This way, we avoid unnecessary code repetition.

Return to inc/template-tags.php. That’s where we’re going to place our navigation function. Add the following, at the end of the file.

if ( ! function_exists( 'shape_content_nav' ) ):
/**
 * Display navigation to next/previous pages when applicable
 *
 * @since Shape 1.0
 */
function shape_content_nav( $nav_id ) {
	global $wp_query, $post;

	// Don't print empty markup on single pages if there's nowhere to navigate.
	if ( is_single() ) {
		$previous = ( is_attachment() ) ? get_post( $post->post_parent ) : get_adjacent_post( false, '', true );
		$next = get_adjacent_post( false, '', false );

		if ( ! $next && ! $previous )
			return;
	}

	// Don't print empty markup in archives if there's only one page.
	if ( $wp_query->max_num_pages < 2 && ( is_home() || is_archive() || is_search() ) )
		return;

	$nav_class = 'site-navigation paging-navigation';
	if ( is_single() )
		$nav_class = 'site-navigation post-navigation';

	?>
	<nav role="navigation" id="<?php echo $nav_id; ?>" class="<?php echo $nav_class; ?>">
		<h1 class="assistive-text"><?php _e( 'Post navigation', 'shape' ); ?></h1>

	<?php if ( is_single() ) : // navigation links for single posts ?>

		<?php previous_post_link( '<div class="nav-previous">%link</div>', '<span class="meta-nav">' . _x( '&larr;', 'Previous post link', 'shape' ) . '</span> %title' ); ?>
		<?php next_post_link( '<div class="nav-next">%link</div>', '%title <span class="meta-nav">' . _x( '&rarr;', 'Next post link', 'shape' ) . '</span>' ); ?>

	<?php elseif ( $wp_query->max_num_pages > 1 && ( is_home() || is_archive() || is_search() ) ) : // navigation links for home, archive, and search pages ?>

		<?php if ( get_next_posts_link() ) : ?>
		<div class="nav-previous"><?php next_posts_link( __( '<span class="meta-nav">&larr;</span> Older posts', 'shape' ) ); ?></div>
		<?php endif; ?>

		<?php if ( get_previous_posts_link() ) : ?>
		<div class="nav-next"><?php previous_posts_link( __( 'Newer posts <span class="meta-nav">&rarr;</span>', 'shape' ) ); ?></div>
		<?php endif; ?>

	<?php endif; ?>

	</nav><!-- #<?php echo $nav_id; ?> -->
	<?php
}
endif; // shape_content_nav

Now we’re going to call this function in our index template. Return to index.php and add the following just before the Loop.

<?php shape_content_nav( 'nav-above' ); ?>

Just after the Loop, add the following code.

<?php shape_content_nav( 'nav-below' ); ?>

Your index.php file should now look like this.

<?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.
* Learn more: http://codex.wordpress.org/Template_Hierarchy
*
* @package Shape
* @since Shape 1.0
*/

get_header(); ?>

<div id="primary" class="content-area">
	<div id="content" class="site-content" role="main">

	<?php if ( have_posts() ) : ?>

		<?php shape_content_nav( 'nav-above' ); ?>

		<?php /* Start the Loop */ ?>
		<?php while ( have_posts() ) : the_post(); ?>

			<?php
				/* Include the Post-Format-specific template for the content.
				 * If you want to overload this in a child theme then include a file
				 * called content-___.php (where ___ is the Post Format name) and that will be used instead.
				 */
				get_template_part( 'content', get_post_format() );
			?>

		<?php endwhile; ?>

		<?php shape_content_nav( 'nav-below' ); ?>

	<?php endif; ?>

	</div><!-- #content .site-content -->
</div><!-- #primary .content-area -->

Look at shape_content_nav() in template-tags.php.

function shape_content_nav( $nav_id ) {

You can see that $nav_id is the variable that holds the parameter we passed to the function when we called it in the index template. $nav_id is equal to ‘nav-above’ and ‘nav-below’ for the top and bottom navigation, respectively. What does the function do with $nav_id? Take a look at this line:

<nav role="navigation" id="<?php echo $nav_id; ?>" class="<?php echo $nav_class; ?>">

$nav_id becomes the ID selector name of the nav element. This allows you to target this particular element with CSS, such as to hide it or to give it a distinct style.

Adding the no-results.php Template Part

Looking back over at index.php, you can see that the Loop loads content.php if posts exist in the database. But what happens if no posts exist? Let’s display a message if posts can’t be found. Just like we did for the post content and for the Aside post format, we’ll place the message in a template part. Open no-results.php, and add the following.

<?php
/**
 * The template part for displaying a message that posts cannot be found.
 *
 * Learn more: http://codex.wordpress.org/Template_Hierarchy
 *
 * @package Shape
 * @since Shape 1.0
 */
?>

<article id="post-0" class="post no-results not-found">
	<header class="entry-header">
		<h1 class="entry-title"><?php _e( 'Nothing Found', 'shape' ); ?></h1>
	</header><!-- .entry-header -->

	<div class="entry-content">
		<?php if ( is_home() && current_user_can( 'publish_posts' ) ) : ?>

			<p><?php printf( __( 'Ready to publish your first post? <a href="%1$s">Get started here</a>.', 'shape' ), admin_url( 'post-new.php' ) ); ?></p>

		<?php elseif ( is_search() ) : ?>

			<p><?php _e( 'Sorry, but nothing matched your search terms. Please try again with some different keywords.', 'shape' ); ?></p>
			<?php get_search_form(); ?>

		<?php else : ?>

			<p><?php _e( 'It seems we can&rsquo;t find what you&rsquo;re looking for. Perhaps searching can help.', 'shape' ); ?></p>
			<?php get_search_form(); ?>

		<?php endif; ?>
	</div><!-- .entry-content -->
</article><!-- #post-0 .post .no-results .not-found -->

Not much different from content.php and content-aside.php. We even have a specialized message to display when no results turn up in a search (that’ll come in handy during the search.php lesson).

Next, we have to include no-results.php in index.php. Open index.php and add the following, right after <?php shape_content_nav( 'nav-below' ); ?> and before <?php endif>.

<?php else : ?>
     <?php get_template_part( 'no-results', 'index' ); ?>

Call up the sidebar and footer

One last thing and we’re done with index.php. At the very end of the file, let’s call in the sidebar.php and footer.php templates with the following code.

<?php get_sidebar(); ?>
<?php get_footer(); ?>

Here’s how the final index.php file should look.

<?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.
* Learn more: http://codex.wordpress.org/Template_Hierarchy
*
* @package Shape
* @since Shape 1.0
*/

get_header(); ?>

<div id="primary" class="content-area">
	<div id="content" class="site-content" role="main">

	<?php if ( have_posts() ) : ?>

		<?php shape_content_nav( 'nav-above' ); ?>

		<?php /* Start the Loop */ ?>
		<?php while ( have_posts() ) : the_post(); ?>

			<?php
				/* Include the Post-Format-specific template for the content.
				 * If you want to overload this in a child theme then include a file
				 * called content-___.php (where ___ is the Post Format name) and that will be used instead.
				 */
				get_template_part( 'content', get_post_format() );
			?>

		<?php endwhile; ?>

		<?php shape_content_nav( 'nav-below' ); ?>

	<?php else : ?>

		<?php get_template_part( 'no-results', 'index' ); ?>

	<?php endif; ?>

	</div><!-- #content .site-content -->
</div><!-- #primary .content-area -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>

How To Create a WordPress Theme

This post is part of the The ThemeShaper WordPress Theme Tutorial: 2nd Edition that will show you how to create a powerful WordPress Theme from scratch. Read it from the beginning and code yourself up something awesome.

  1. WordPress Theme Tutorial Introduction
  2. Developing Theme Sense
  3. Theme Development Tools
  4. Creating a Theme HTML Structure
  5. Template and Directory Structure
  6. Setting Up Your Theme Functions
  7. Secure Your WordPress Theme
  8. The Header Template
  9. The Index Template
  10. The Single Post, Post Attachment, & 404 Templates
  11. The Comments Template
  12. The Search Template & The Page Template
  13. The Archive Template
  14. The Sidebar Template & The Footer Template
  15. Reset-Rebuild Theme CSS & Define Your Layouts
  16. Custom Background & Custom Header
  17. Distributing Your WordPress Theme

30 thoughts on “The WordPress Theme Index Template

  1. Pingback: The WordPress Theme Index Template

  2. grappler says:

    Hi, I just wanted to say that the posted on string would be a nightmare to translate. This is how long the string would be.
    Posted on %4$s by %7$s

    If you used the code like this…

    function shape_posted_on() {
    printf( __( 'Posted on %2$s by %4$s', 'shape' ),
    'meta-prep meta-prep-author posted',
    sprintf( '%3$s',
    esc_url( get_permalink() ),
    esc_attr( get_the_time() ),
    esc_html( get_the_date() )
    ),
    'byline',
    sprintf( '%3$s',
    esc_url( get_author_posts_url( get_the_author_meta( 'ID' ) ) ),
    esc_attr( sprintf( __( 'View all posts by %s', 'shape' ), get_the_author() ) ),
    esc_html( get_the_author() )
    )
    );
    }
    endif;

    …then the string would only be this long
    Posted on %2$s by %4$s

    The translator would need to check that the code has been added properly.

  3. Thank you very much for this series!

    At this point, am I supposed to see the posts on my site? Because now all I can see is this:

    Design Web Design
    Just another WordPress site
    Menu
    Skip to content

    Accueil
    Exemple 2
    Sample Page

    Thank you! Mado

    • Hi Mado! Yes, the posts should appear at this point in the tutorial. Can you try pasting the code from this lesson again? There were a few bugs on my part that I’ve just fixed, and I have updated the code to reflect the changes.

  4. Marie says:

    Thank you so much for writing this tutorial. I have just started learning PHP and it is very difficult for me to grasp. You really have helped a lot by explaining how and why things work!

  5. jellboi says:

    I don’t know where am I going wrong, but after adding the code in content.php it throws me error

    “Fatal error: Call to undefined function shape_posted_on() [path][line no]“

    and I observed that it shows the same error for all functions defined in template-tags.php
    It seems it is not able to reference the template-tags.php file.
    Need help!!!

  6. Thanks for the articles. I’m trying the theme creation now. It’s first time. So really copy and paste. So easy. But I found the bit. On the Loop code, above the starting footer line ( comment line ). PHP close tag are nothing. I was easy understanding. But beginners are … difficult. So please the check and fix. If my theme creation are finished ( on the mail domain ), I’m invite to the articles for friends and many social sites. Yes, great articles and team. Thanks for all ( Sorry, broken english … ).

  7. Nate Clay says:

    Should my site me displaying excerpts correctly at this point? It doesn’t seem to want to display them and I can’t figure out what the problem is. Thanks.

  8. sofian says:

    Hello again, i dont understand why WP-internal GETs have to be escaped, arent they secure since they come from inside WP?
    i refer to the function shaped_on:
    esc_url( get_permalink() )
    esc_attr( get_the_time() ) …
    Thanks for again a bit more understanding

    • Hi there, thanks for your feedback – I’m glad you found it useful. Since this tutorial is now nearly a year old, naturally some things have changed since it was created. The principles presented in each lesson are still relevant.

      We will be making some updates to the tutorial in the future – keep your eye out.

  9. C says:

    Great work. I do find the rollover for the code really really annoying though. It’s like I want it to just stay open while I compare things but it can’t. Unless you copy everything into a document or something. Also really hard to scroll left and right

  10. Great tutorial! Just one question: in the loop section in line 8 the if statement is commented “// if ( ‘post’ == get_post_type() ) :” but the “endif;” is not. Will that not give an error?

  11. Failips says:

    Hi, when i open my theme in wordpress it write this error:

    Parse error: syntax error, unexpected ‘ ‘ (T_STRING) in C:\xampp\htdocs\wordpress\wp-content\themes\MyFirstTheme\content.php on line 34

    the codearound that line is:

      
            
                

    where line 34 is this:

                    $categories_list = get_the_category_list( __( ‘, ‘, ‘shape’ ) );

    Please help me, Thanks :)

  12. Serge says:

    I’m learning to copy-paste, how useful. There’s hardly any explanation on what’s actually going on.
    This tutorial could be renamed “Copying a templates which does things.”

    Too bad all the effort are going to waste due to the lack of “when” and “why” while there’s plenty of “how”.

    • Sorry you haven’t found the tutorial more useful, Serge. The authors have tried to balance explanations with practical examples. Hopefully you’ll find future tutorials more helpful to your theme-development learning process.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s