When WordPress 2.7 came out, there were a lot of new features, most of which probably passed by under your radar. I know I missed a bunch and thankfully, I am starting to notice how much more powerful WordPress has become. I had someone request making my Magazine Basic theme function with nested comments and that started me on researching just how this new WP feature worked. It ended up being a harder nut to crack than I first thought, but crack it I did (though I probably spend way too much time on it).

There are quite a few steps to making your theme work with nested comments but the easiest thing to do to start off is open up your theme’s comments.php file and replace it with this:

<?php

// Do not delete these lines
if (!empty($_SERVER['SCRIPT_FILENAME']) && 'comments.php' == basename($_SERVER['SCRIPT_FILENAME']))
die ('Please do not load this page directly. Thanks!');

if ( post_password_required() ) { ?>
<p class="nocomments">This post is password protected. Enter the password to view comments.</p>
<?php
return;
}
?>

<!-- You can start editing here. -->

<?php if ( have_comments() ) : ?>
<h3 id="comments"><?php comments_number('No Responses', 'One Response', '% Responses' );?> to &#8220;<?php the_title(); ?>&#8221;</h3>

<div class="navigation">
<div class="alignleft"><?php previous_comments_link() ?></div>
<div class="alignright"><?php next_comments_link() ?></div>
</div>

<ol class="commentlist">
<?php wp_list_comments(); ?>
</ol>

<div class="navigation">
<div class="alignleft"><?php previous_comments_link() ?></div>
<div class="alignright"><?php next_comments_link() ?></div>
</div>
<?php else : // this is displayed if there are no comments so far ?>

<?php if ('open' == $post->comment_status) : ?>
<!-- If comments are open, but there are no comments. -->

<?php else : // comments are closed ?>
<!-- If comments are closed. -->
<p class="nocomments">Comments are closed.</p>

<?php endif; ?>
<?php endif; ?>

<?php if ('open' == $post->comment_status) : ?>

<div id="respond">

<h3><?php comment_form_title( 'Leave a Reply', 'Leave a Reply to %s' ); ?></h3>

<div class="cancel-comment-reply">
<small><?php cancel_comment_reply_link(); ?></small>
</div>

<?php if ( get_option('comment_registration') && !$user_ID ) : ?>
<p>You must be <a href="<?php echo get_option('siteurl'); ?>/wp-login.php?redirect_to=<?php echo urlencode(get_permalink()); ?>">logged in</a> to post a comment.</p>
<?php else : ?>

<form action="<?php echo get_option('siteurl'); ?>/wp-comments-post.php" method="post" id="commentform">

<?php if ( $user_ID ) : ?>

<p>Logged in as <a href="<?php echo get_option('siteurl'); ?>/wp-admin/profile.php"><?php echo $user_identity; ?></a>. <a href="<?php echo wp_logout_url(get_permalink()); ?>" title="Log out of this account">Log out &raquo;</a></p>

<?php else : ?>

<p><input type="text" name="author" id="author" value="<?php echo $comment_author; ?>" size="22" tabindex="1" <?php if ($req) echo "aria-required='true'"; ?> />
<label for="author"><small>Name <?php if ($req) echo "(required)"; ?></small></label></p>

<p><input type="text" name="email" id="email" value="<?php echo $comment_author_email; ?>" size="22" tabindex="2" <?php if ($req) echo "aria-required='true'"; ?> />
<label for="email"><small>Mail (will not be published) <?php if ($req) echo "(required)"; ?></small></label></p>

<p><input type="text" name="url" id="url" value="<?php echo $comment_author_url; ?>" size="22" tabindex="3" />
<label for="url"><small>Website</small></label></p>

<?php endif; ?>

<!--<p><small><strong>XHTML:</strong> You can use these tags: <code><?php echo allowed_tags(); ?></code></small></p>-->

<p><textarea name="comment" id="comment" cols="100%" rows="10" tabindex="4"></textarea></p>

<p><input name="submit" type="submit" id="submit" tabindex="5" value="Submit Comment" />
<?php comment_id_fields(); ?>
</p>
<?php do_action('comment_form', $post->ID); ?>

</form>

<?php endif; // If registration required and not logged in ?>
</div>

<?php endif; // if you delete this the sky will fall on your head ?>

Now, you need to make sure that nested comments are activated on your WordPress install by going to your admin panel => Settings => Discussion and checking “Enable threaded (nested) comments” Set the level to whatever you want. I like using 2 levels.

If you return to a post page on your site, you should now see reply buttons after each post. And if you click reply, you will noticed that not much happens other than “Click here to cancel reply.” appearing above your reply window. It is a little too subtle for me, so luckily there is a great Javascript hook already installed that you can call by just adding this:

<?php if ( is_singular() ) wp_enqueue_script( 'comment-reply' ); ?>

to you header.php file above <?php wp_head(); ?>.

Now when you click reply, the reply box appears directly below the comment you want to reply to. This is a great little feature and it makes your comments section more intuitive for your user.

The only thing left, really, is styling your comments. With WordPress 2.7, one hook creates the whole comment loop, as opposed to before when you had to call the avatar, the comment text, the date and all that jazz. There is a lot happening with <?php wp_list_comments(); ?> and though some might find it awesome that you don’t have to worry about adding tons of code, others might not like the fact that you can’t edit what appears in your comments and where. There is a solution to this that I will show you below, but for now lets talk about CSS.

WP 2.7 nested comments introduce some new classes and ids for you to style as you wish. It is a pretty long list though.

ol.commentlist {}
ol.commentlist li {}
ol.commentlist li.alt {}
ol.commentlist li.bypostauthor {}
ol.commentlist li.byuser {}
ol.commentlist li.comment-author-admin {}
ol.commentlist li.comment {}
ol.commentlist li.comment div.comment-author {}
ol.commentlist li.comment div.vcard {}
ol.commentlist li.comment div.vcard cite.fn {}
ol.commentlist li.comment div.vcard cite.fn a.url {}
ol.commentlist li.comment div.vcard img.avatar {}
ol.commentlist li.comment div.vcard img.avatar-32 {}
ol.commentlist li.comment div.vcard img.photo {}
ol.commentlist li.comment div.vcard span.says {}
ol.commentlist li.comment div.commentmetadata {}
ol.commentlist li.comment div.comment-meta {}
ol.commentlist li.comment div.comment-meta a {}
ol.commentlist li.comment * {} - (p, em, strong, blockquote, ul, ol, etc.)
ol.commentlist li.comment div.reply {}
ol.commentlist li.comment div.reply a {}
ol.commentlist li.comment ul.children {}
ol.commentlist li.comment ul.children li {}
ol.commentlist li.comment ul.children li.alt {}
ol.commentlist li.comment ul.children li.bypostauthor {}
ol.commentlist li.comment ul.children li.byuser {}
ol.commentlist li.comment ul.children li.comment {}
ol.commentlist li.comment ul.children li.comment-author-admin {}
ol.commentlist li.comment ul.children li.depth-2 {}
ol.commentlist li.comment ul.children li.depth-3 {}
ol.commentlist li.comment ul.children li.depth-4 {}
ol.commentlist li.comment ul.children li.depth-5 {}
ol.commentlist li.comment ul.children li.odd {}
ol.commentlist li.even {}
ol.commentlist li.odd {}
ol.commentlist li.parent {}
ol.commentlist li.pingback {}
ol.commentlist li.pingback div.comment-author {}
ol.commentlist li.pingback div.vcard {}
ol.commentlist li.pingback div.vcard cite.fn {}
ol.commentlist li.pingback div.vcard cite.fn a.url {}
ol.commentlist li.pingback div.vcard span.says {}
ol.commentlist li.pingback div.commentmetadata {}
ol.commentlist li.pingback div.comment-meta {}
ol.commentlist li.pingback div.comment-meta a {}
ol.commentlist li.pingback * {} - (p, em, strong, blockquote, ul, ol, etc.)
ol.commentlist li.pingback div.reply {}
ol.commentlist li.pingback div.reply a {}
ol.commentlist li.pingback ul.children {}
ol.commentlist li.pingback ul.children li {}
ol.commentlist li.pingback ul.children li.alt {}
ol.commentlist li.pingback ul.children li.bypostauthor {}
ol.commentlist li.pingback ul.children li.byuser {}
ol.commentlist li.pingback ul.children li.comment {}
ol.commentlist li.pingback ul.children li.comment-author-admin {}
ol.commentlist li.pingback ul.children li.depth-2 {}
ol.commentlist li.pingback ul.children li.depth-3 {}
ol.commentlist li.pingback ul.children li.depth-4 {}
ol.commentlist li.pingback ul.children li.depth-5 {}
ol.commentlist li.pingback ul.children li.odd {}
ol.commentlist li.thread-alt {}
ol.commentlist li.thread-even {}
ol.commentlist li.thread-odd {}

The depth actually goes down to level 10 but I stopped at level 5 above. Hopefully you get the idea. There are a lot of elements to style but of course, you don’t have to style them all. For my Magazine Basic theme, I used only the following:

ol.commentlist { list-style:none; margin:0; padding:0; }
ol.commentlist li { border:1px solid #d5d5d5; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px; margin:0 0 10px; padding:5px 7px 5px 64px; position:relative; }
ol.commentlist li.pingback comment-author { padding:0 170px 0 0; }
ol.commentlist li div.vcard { font-weight:bold; font-size: 14px; line-height: 16px; font-family: helvetica,arial,sans-serif; }
ol.commentlist li div.vcard cite.fn { font-style:normal; font-size: 11px; }
ol.commentlist li div.vcard cite.fn a.url { color:#cc0000; text-decoration:none; }
ol.commentlist li div.vcard cite.fn a.url:hover { color:#000; }
ol.commentlist li div.vcard img.avatar { background: #fff; border:1px solid #aaa; padding: 5px; left:7px; position:absolute; top:7px; }
ol.commentlist li div.comment-meta { font-weight:bold; font-size: 10px; line-height: 16px; font-family: helvetica,arial,sans-serif; position:absolute; right:10px; text-align:right; top:5px; }
ol.commentlist li div.comment-meta a { color:#205B87; text-decoration:none; }
ol.commentlist li p { font-weight:normal; font-size: 12px; line-height: 16px; font-family: helvetica,arial,sans-serif; margin:5px 0 12px; }
ol.commentlist li ul { font-weight:normal; font-size: 12px; line-height: 16px; font-family: helvetica,arial,sans-serif; list-style:square; margin:0 0 12px; padding:0; }
ol.commentlist li div.reply { background:#999; border:1px solid #666; border-radius:2px; -moz-border-radius:2px; -webkit-border-radius:2px; color:#fff; font:bold 9px/1 helvetica,arial,sans-serif; padding:6px 5px 4px;  text-align:center; width:36px; }
ol.commentlist li div.reply:hover { background:#cc0000; border:1px solid #cc0000; }
ol.commentlist li div.reply a { color:#fff; text-decoration:none; text-transform:uppercase; }
ol.commentlist li ul.children { list-style:none; margin:12px 0 0; text-indent:0; }
ol.commentlist li ul.children li.depth-2 { margin:0 0 3px; }
ol.commentlist li ul.children li.depth-3 { margin:0 0 3px; }
ol.commentlist li ul.children li.depth-4 { margin:0 0 3px; }
ol.commentlist li ul.children li.depth-5 { margin:0 0 3px; }
ol.commentlist ul.children li.odd { background:#fff; }
ol.commentlist ul.children li.even { background:#f6f6f6; }
ol.commentlist li.pingback div.vcard { padding:0 170px 0 0; }

If you have your nested comment level set to 3 you should now have something that looks like this:

threaded

It all works great but there are a few things that I wanted to change. They don’t make it that easy to manipulate things but I figured it out and added a few touches of my own to get it to perform how I wanted it to.

To change the size of the avatar you can change line 25 in comments.php to this:

<?php wp_list_comments('avatar_size=48'); ?>

The default avatar size is 32 and you can change it to anything you want.

If you don’t want your comments to be displayed as the default unordered list (ul), you can change the style to divs or an ordered list (ol) by using the following;

<?php wp_list_comments('style=div'); ?>

Replace div with ol or ul but be sure to also change line 24 and 26 which currently is set for ordered lists so that you have something like this:

<div class="commentlist">
<?php wp_list_comments('style=div'); ?>
</div>

If you want to only display comments, trackbacks, pingbacks (trackbacks and pings) or pings, you can use the following:

<?php wp_list_comments('type=comment'); ?>

Now for those people who want total control over their code. This is not recommended to those who are inexperienced with coding. This actually bypasses all of the internal WordPress functionality in regards to comments and lets you customize everything that the wp_list_comments hook spits out. This is a two step process.

First add:

<?php wp_list_comments('callback=mytheme_comment'); ?>

Then go to your functions.php file (or create a functions.php file if you don’t already have one) and add this:

<?php
function mytheme_comment($comment, $args, $depth) {
$GLOBALS['comment'] = $comment; ?>
<li <?php comment_class(); ?> id="li-comment-<?php comment_ID() ?>">
<div id="comment-<?php comment_ID(); ?>">
<div class="comment-author vcard">
<?php echo get_avatar($comment,$size='36',$default='<path_to_url>' ); ?>

<?php printf(__('<cite class="fn">%s</cite>  <span class="says">says:</span>'), get_comment_author_link()) ?>
</div>
<?php if ($comment->comment_approved == '0') : ?>
<em><?php _e('Your comment is awaiting moderation.') ?></em>
<br />
<?php endif; ?>

<div class="comment-meta commentmetadata"><a href="<?php echo htmlspecialchars(get_comment_link( $comment->comment_ID )) ?>">
<?php printf(__('%1$s at %2$s'), get_comment_date(),get_comment_time()) ?></a><?php edit_comment_link(__('(Edit)'),'  ','') ?></div>

<?php comment_text() ?>
<?php if($args['max_depth']!=$depth) { ?>
<div class="reply">
<?php comment_reply_link(array_merge($args, array('depth' => $depth, 'max_depth' => $args['max_depth']))) ?>
</div>
<?php } ?>
</div>
<?php
}
?>

Here there is a lot that you can do. You can set your avatar size, change your classes and ids, change you awaiting moderation text or pretty much just rearrange the entire layout. I even added a little if statement at the end to only display the reply button if your comments can go down another level (this took a long time to figure out but is totally worth it in my books).

With everything above in place, your WordPress theme should now be fit to work with nested comments and you should be able to control how they are displayed to your hearts content.