In this tutorial, I’ll teach you how to adapt a simple HTML blog layout into a WordPress theme. We’ll be using this static page as starting point.
Grab the code from Github
The Brilliante Layout project source code for this theme is available on Github, including the dummy content for posts, pages and such. We won’t start from scratch, we’re building it using our static version base.
I suggest you to follow along snippet by snippet, but, if you happen to be as lazy as me, the data folder will help you getting up and running with the content data, it also includes instructions in case you don’t know how to import it to your fresh WordPress installation.
Files and structure
First, we’ll need to adapt our previous static version structure to the new WordPress theme conventions. Like checking if the site still works after changing the extension to the index.html to PHP, moving stylesheets, javascripts, images and URLs code relations.
We could kick out a simple theme by just serving an index.php and a style.css, but we’ll need more than that to give Brilliante Layout all the WordPress shine we can. There’s a good graphic you should check to understand the template hierarchy.
Codex is your friend
When developing a theme, we are basically choping up HTML code into PHP files. Here’s this awesome article explaining every single theme file in a ridiculously simple way.
It’s always a good idea to have an open tab with the Codex: yes, there’s no better place than that to learn or find documentation almost about anything WordPress-related. I’ll be linking to the Codex frequently all along this tutorial.
style.css
/*
Theme Name: Brillante Layout
Theme URI: https://github.com/tatygrassini/Brilliante_Layout
Description: WordPress theme version for the Brillante Layout.
Author: Taty Grassini
Author URI: tatygrassini.github.com
Version: 1.0
Tags: brillante, white, blue, two-column, fixed-width, right-sidebar
*/
For better performance, we won’t use the @import
statement here to call our styles. We’ll use the style file to render the theme info only.
header.php
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title><?php bloginfo('name'); ?></title>
<link rel="shortcut icon" href="favicon.ico" >
<link rel="stylesheet" media="screen" href="<?php bloginfo('template_url')?>/css/style.css" />
<!--[if lte IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<link rel="stylesheet" media="screen" href="<?php bloginfo('template_url')?>/css/ie.css" />
<![endif]-->
<?php wp_head(); ?>
</head>
<?php echo is_single() || is_page() ? "<body class='single'>" : "<body>"; ?>
<header>
<div class="top">
<div class="top-content">
<nav>
<?php wp_nav_menu( array( 'theme_location' => 'primary' ) ); ?>
</nav>
<div class="block">
<p><a href="<?php bloginfo('rss2_url'); ?>">Grab our feeds</a></p>
<form id="search" action="<?php get_option('home') ?>" method="get">
<input name="s" id="s" class="search_input" placeholder="Search...">
<input type="submit" class="search_submit" value="Go">
</form>
</div><!-- .block -->
</div><!-- .top-content -->
</div><!-- .top -->
<div class="logo">
<a href="<?php bloginfo('url'); ?>"><h1 class="notext"><?php bloginfo('name'); ?></h1></a>
<p><?php bloginfo ('description'); ?></p>
</div><!-- .logo -->
See the bloginfo
function all over the place. It’s one of the most useful functions in theme development. Highlighted lines show you some of its common parameters, such as site name, RSS, main URL, description and template URL.
See line 9: instead of using the bloginfo('stylesheet_url')
function, we’re using the bloginfo('template_url')?>/css/style.css
, telling the CMS to go straight to our css folder.
Line 20: if it’s a single view or a page, assign the single
class to the <body>
element, so we can manipulate different styles with CSS.
Then, the usual stuff: navigation and the search form.
<footer>
<div class="footer-content">
<div class="footer-widget footer-first">
<?php if (dynamic_sidebar('footer-first')) : else : ?>
<h2>Links</h2>
<ul>
<?php wp_list_bookmarks('title_li=&categorize=0&limit=6'); ?>
</ul>
<?php endif; ?>
</div>
<div class="footer-widget footer-second">
<?php if (dynamic_sidebar('footer-second')) : else : ?>
<h2>Watch us on flickr</h2>
<ul class="flickr">
<!-- Pulling data from Flickr API with JSON and jQuery -->
</ul>
<?php endif; ?>
</div>
<div class="footer-widget footer-third last">
<?php if (dynamic_sidebar('footer-third')) : else : ?>
<h2>Tweet tweet!</h2>
<div id="twitter">
<script>
typeof getTwitters!="function"&&function(){var a={},b=0;!function(a,b){function m(a){l=1;while(a=c.shift())a()}var c=[],d,e,f=!1,g=b.documentElement,h=g.doScroll,i="DOMContentLoaded",j="addEventListener",k="onreadystatechange",l=/^loade|c/.test(b.readyState);b[j]&&b[j](i,e=function(){b.removeEventListener(i,e,f),m()},f),h&&b.attachEvent(k,d=function(){/^c/.test(b.readyState)&&(b.detachEvent(k,d),m())}),a.domReady=h?function(b){self!=top?l?b():c.push(b):function(){try{g.doScroll("left")}catch(c){return setTimeout(function(){a.domReady(b)},50)}b()}()}:function(a){l?a():c.push(a)}}(a,document),window.getTwitters=function(c,d,e,f){b++,typeof d=="object"&&(f=d,d=f.id,e=f.count),e||(e=1),f?f.count=e:f={},!f.timeout&&typeof f.onTimeout=="function"&&(f.timeout=10),typeof f.clearContents=="undefined"&&(f.clearContents=!0),f.twitterTarget=c,typeof f.enableLinks=="undefined"&&(f.enableLinks=!0),a.domReady(function(a,b){return function(){function f(){a.target=document.getElementById(a.twitterTarget);if(!!a.target){var f={limit:e};f.includeRT&&(f.include_rts=!0),a.timeout&&(window["twitterTimeout"+b]=setTimeout(function(){twitterlib.cancel(),a.onTimeout.call(a.target)},a.timeout*1e3));var g="timeline";d.indexOf("#")===0&&(g="search"),d.indexOf("/")!==-1&&(g="list"),a.ignoreReplies&&(f.filter={not:new RegExp(/^@/)}),twitterlib.cache(!0),twitterlib[g](d,f,function(d,e){clearTimeout(window["twitterTimeout"+b]);var f=[],g=d.length>a.count?a.count:d.length;f=["<ul>"];for(var h=0;h<g;h++){d[h].time=twitterlib.time.relative(d[h].created_at);for(var i in d[h].user)d[h]["user_"+i]=d[h].user[i];a.template?f.push("<li>"+a.template.replace(/%([a-z_-.]*)%/ig,function(b,c){var e=d[h][c]+""||"";c=="text"&&(e=twitterlib.expandLinks(d[h])),c=="text"&&a.enableLinks&&(e=twitterlib.ify.clean(e));return e})+"</li>"):a.bigTemplate?f.push(twitterlib.render(d[h])):f.push(c(d[h]))}f.push("</ul>"),a.clearContents?a.target.innerHTML=f.join(""):a.target.innerHTML+=f.join(""),a.callback&&a.callback(d)})}}function c(b){var c=a.enableLinks?twitterlib.ify.clean(twitterlib.expandLinks(b)):twitterlib.expandLinks(b),d="<li>";a.prefix&&(d+='<li><span className="twitterPrefix">',d+=a.prefix.replace(/%(.*?)%/g,function(a,c){return b.user[c]}),d+="</span></li>"),d+='<span className="twitterStatus">'+twitterlib.time.relative(b.created_at)+"</span>",d+='<span className="twitterTime">'+b.text+"</span>",a.newwindow&&(d=d.replace(/<a href/gi,'<a target="_blank" href'));return d}typeof twitterlib=="undefined"?setTimeout(function(){var a=document.createElement("script");a.onload=a.onreadystatechange=function(){typeof window.twitterlib!="undefined"&&f()},a.src="//remy.github.com/twitterlib/twitterlib.js";var b=document.head||document.getElementsByTagName("head")[0];b.insertBefore(a,b.firstChild)},0):f()}}(f,b))}}()
getTwitters('twitter',{id:'<?php echo get_option('brilliante_layout_twitter_user'); ?>',count:1,enableLinks:true,ignoreReplies:true,clearContents:true,template:'<p style="font: italic 15px/23px Georgia,serif;color:#EDEDED;"><em>“%text%”</em></p> <p style="color:#EDEDED;line-height:23px;" class="cufon"><a href="https://twitter.com/%user_screen_name%/statuses/%id%/">%time%</a> <br />From %source%</p> <h2><a href="https://twitter.com/<?php echo get_option('brilliante_layout_twitter_user'); ?>/">Follow us on Twitter!</a></h2>',callback:function(){Cufon.replace('p.cufon, h2')}});
</script>
</div>
<?php endif; ?>
</div>
</div><!-- .footer-content -->
</footer>
<div id="bottom">
<?php wp_nav_menu( array( 'theme_location' => 'primary', 'link_before' => ' | ')); ?>
<p>Copyright <?php echo date('Y'); ?> − <a href="<?php bloginfo('url'); ?>" title="<?php bloginfo('name'); ?>"><?php bloginfo('name'); ?></a> − All rights reserved</p>
</div><!-- #bottom -->
<script src="//code.jquery.com/jquery-latest.js"></script>
<script src="<?php bloginfo('template_url')?>/js/cufon-yui.js"></script>
<script src="<?php bloginfo('template_url')?>/js/frutiger.font.js"></script>
<script src="<?php bloginfo('template_url')?>/js/slides.min.jquery.js"></script>
<script src="<?php bloginfo('template_url')?>/js/func.js"></script>
<!--[if IE 6]>
<script src="<?php bloginfo('template_url')?>/js/belatedPNG.js"></script>
<script>
DD_belatedPNG.fix('*');
</script>
<![endif]-->
<script> Cufon.now(); </script>
<!-- Google Analytics and other scripts here -->
<?php wp_footer() ?>
</body>
</html>
Here, we’re widgetizing all the three block areas in the footer. And on line 27, some JavaScript to render my last Twit. The else
statement is giving us the chance to render that content until the user drops some widgets.
<aside>
<ul class="sidebar">
<?php if (dynamic_sidebar('sidebar')) : else : ?>
<li class="sidebar-widget">
<div class="sidebar-widget">
<h2><span>Author</span></h2>
<img class="avatar" width="99" height="99" alt="Mahmoud Khaled" src="https://1.gravatar.com/avatar/beb66a755ea479a2f10fc19c7c29c054?s=99&d=Gravatar+Logo&r=G">
<h2>Mahmoud Khaled</h2>
<p><em>Web & Graphics Designer</em></p>
<p>Sectetur adipisicing elit, sf sed dos eiusmod tempor incididunt utto po Web and graphics designer!</p>
</div>
</li>
<li class="sidebar-widget">
<div class="sidebar-widget">
<h2><span>Categories</span></h2>
<ul>
<?php wp_list_categories('title_li='); ?>
</ul>
</div>
</li>
<li class="sidebar-widget">
<div class="sidebar-widget">
<h2><span><?php _e( 'Meta', 'brilliante_layout' ); ?></span></h2>
<ul>
<?php wp_register(); ?>
<li><?php wp_loginout(); ?></li>
<?php wp_meta(); ?>
</ul>
</div>
</li>
<?php endif; ?>
</ul>
</aside>
</div><!-- #content -->
Sidebar is widgetized as well. Same deal as before with the else
statement: unless one or more widgets assigned, render author, categories and log-in box.
index.php
<?php get_header(); ?>
<?php include (TEMPLATEPATH . '/inc/featured.php' ); ?>
</header>
<div id="content">
<div class="main">
<h2><span>Latest from the blog</span></h2>
<?php if (have_posts()) : while (have_posts()) : the_post(); ?>
<?php include (TEMPLATEPATH . '/inc/post.php' ); ?>
<?php endwhile;
else : ?>
<p>Page not found.</p>
<?php endif; ?>
<?php include (TEMPLATEPATH . '/inc/nav.php' ); ?>
</div><!-- main -->
<?php get_sidebar(); ?>
<?php get_footer(); ?>
Index ties everything up. The get_header
, get_sidebar
and get_footer
are pretty self-explanatory. From the top, we include the featured area slideshow snippet from the inc folder, only used in the home page, then the loop on line 10, the post snippet on line 12 and the navigation snippet included on line 22.
functions.php
<?php
// ----------------- Menus w/fallback for older WP versions --------------------
//
register_nav_menu( 'primary', __( 'Primary Menu', 'brilliante_layout' ) );
// Get our wp_nav_menu() fallback, wp_page_menu(), to show a home link.
function brilliante_layout_page_menu_args( $args ) {
$args['show_home'] = true;
return $args;
}
add_filter( 'wp_page_menu_args', 'brilliante_layout_page_menu_args' );
// ----------------- Widget-Ready Sidebar ---------------------------------------
//
if ( function_exists('register_sidebar') )
register_sidebar(array(
'name' => __( 'Sidebar', 'brilliante_layout' ),
'id' => 'sidebar',
'before_widget' => '<li class="sidebar-widget"><div class="sidebar-widget" id="%1$s">',
'after_widget' => '</div></li>',
'before_title' => '<h2 class="sidebar-widget"><span>',
'after_title' => '</span></h2>',
));
// ----------------- Widget-Ready Footer ----------------------------------------
//
if ( function_exists('register_sidebar') )
register_sidebar(array(
'name' => __( 'Footer First', 'brilliante_layout' ),
'id' => 'footer-first',
'before_widget' => '<div class="footer-widget">',
'after_widget' => '</div>',
'before_title' => '<h2>',
'after_title' => '</h2>',
));
if ( function_exists('register_sidebar') )
register_sidebar(array(
'name' => __( 'Footer Second', 'brilliante_layout' ),
'id' => 'footer-second',
'before_widget' => '<div class="footer-widget">',
'after_widget' => '</div>',
'before_title' => '<h2>',
'after_title' => '</h2>',
));
if ( function_exists('register_sidebar') )
register_sidebar(array(
'name' => __( 'Footer Third', 'brilliante_layout' ),
'id' => 'footer-third',
'before_widget' => '<div class="footer-widget">',
'after_widget' => '</div>',
'before_title' => '<h2>',
'after_title' => '</h2>',
));
// ----------------- Post Featured Images support -------------------------------
// Watch out for the array( 'post','slides' ) in here...
if ( function_exists( 'add_theme_support' ) ) { // Added in 2.9
add_theme_support( 'post-thumbnails', array( 'post','slides' ) ); // Add featured images to posts
set_post_thumbnail_size( 140, 140, true ); // Normal post thumbnails
add_image_size( 'single-post-thumbnail', 542,220, true ); // Single Post thumbnail size
}
// ----------------- Remove code from the <head> --------------------------------
//
remove_action('wp_head', 'rsd_link');
remove_action('wp_head', 'feed_links_extra', 3);
remove_action('wp_head', 'wlwmanifest_link');
function hcwp_remove_version() {return '';}
add_filter('the_generator', 'hcwp_remove_version');
// ----------------- Disable the admin bar in 3.1 -------------------------------
//show_admin_bar( false );
?>
This huge amount of code has comments, giving us a hint of what these snippets do. This will take care of our menues, widgetized areas, post featured images, meta tags cleaning and the chance to get rid of the admin bar (in the case you hate it).
Oh yeah, there’s more!
We’ll also need a single view for our posts, pages, archives, search results page and a page template for our blog link.
single.php
<?php get_header(); ?>
</header>
<div id="content">
<div class="main">
<h2 id="post-<?php the_ID(); ?>"><span><?php the_title(); ?></span></a></h2>
<?php if (have_posts()) : while (have_posts()) : the_post(); ?>
<div class="post">
<div class="post-single">
<?php if(has_post_thumbnail()) { ?>
<a href="<?php the_permalink() ?>"><?php the_post_thumbnail( 'single-post-thumbnail' ); ?></a>
<?php } else {
echo '<img src="'.get_bloginfo("template_url").'/css/img/no-img-542x220.gif" />';
} ?>
<div class="text">
<?php the_content(); ?>
<div class="readMore">
<span>Author: </span><em><?php the_author_posts_link(); ?></em><br />
</div><!-- .readMore -->
<div class="cat-date">
<span class="posted">Posted in: </span><em><?php the_category(', ') ?></em></span>
</div><!-- .cat-date -->
</div><!-- .text -->
</div><!-- .post-single -->
</div><!-- .post -->
<?php endwhile;
else : ?>
<p>Page not found.</p>
<?php endif; ?>
<div id="comments">
<?php comments_template(); ?>
</div>
</div><!-- main -->
<?php get_sidebar(); ?>
<?php get_footer(); ?>
At the top, there’s no need for the featured area slideshow on the single view.
Line 14: we’ll render the featured post image for the single view, bigger image, its dimensions have been declared in the functions file.
Line 41: the comments_template
functions is calling… well, you know what…
page.php
<?php get_header(); ?>
</header>
<div id="content">
<div class="main">
<h2><span><?php the_title(); ?></span></h2>
<?php if ( have_posts() ) while ( have_posts() ) : the_post(); ?>
<div class="post">
<div class="post-single">
<?php the_content(); ?>
<?php edit_post_link( __( 'Edit', 'brilliante_layout' ), '', '' ); ?>
</div><!-- .post-single -->
</div><!-- .post -->
<?php endwhile; ?>
</div><!-- main -->
<?php get_sidebar(); ?>
<?php get_footer(); ?>
archive.php
<?php get_header(); ?>
<div id="content">
<div class="main">
<?php if (have_posts()) : ?>
<?php $post = $posts[0]; // Hack. Set $post so that the_date() works. ?>
<?php /* If this is a category archive */ if (is_category()) { ?>
<h2><span>Archive for the ‘<?php single_cat_title(); ?>’ Category</span></h2>
<?php /* If this is a tag archive */ } elseif( is_tag() ) { ?>
<h2><span>Posts Tagged ‘<?php single_tag_title(); ?>’</span></h2>
<?php /* If this is a daily archive */ } elseif (is_day()) { ?>
<h2><span>Archive for <?php the_time('F jS, Y'); ?></span></h2>
<?php /* If this is a monthly archive */ } elseif (is_month()) { ?>
<h2><span>Archive for <?php the_time('F, Y'); ?></span></h2>
<?php /* If this is a yearly archive */ } elseif (is_year()) { ?>
<h2 class="pagetitle"><span>Archive for <?php the_time('Y'); ?></span></h2>
<?php /* If this is an author archive */ } elseif (is_author()) { ?>
<h2 class="pagetitle"><span>Author Archive</span></h2>
<?php /* If this is a paged archive */ } elseif (isset($_GET['paged']) && !empty($_GET['paged'])) { ?>
<h2 class="pagetitle"><span>Blog Archives</span></h2>
<?php } ?>
<?php include (TEMPLATEPATH . '/inc/nav.php' ); ?>
<?php while (have_posts()) : the_post(); ?>
<div class="post">
<h3 id="post-<?php the_ID(); ?>"><a href="<?php the_permalink() ?>"><?php the_title(); ?></a></h3>
</div><!-- .post -->
<?php endwhile; ?>
<?php include (TEMPLATEPATH . '/inc/nav.php' ); ?>
<?php else : ?>
<h2><span>Nothing found</span></h2>
<?php endif; ?>
</div><!-- main -->
<?php get_sidebar(); ?>
<?php get_footer(); ?>
The code on the first half will give us a heading with the proper condition for our query.
search.php
<?php get_header(); ?>
<div id="content">
<div class="main">
<?php if (have_posts()) : ?>
<h2><span>Search results for: <?php the_search_query(); ?></span></h2>
<?php while (have_posts()) : the_post(); ?>
<?php include (TEMPLATEPATH . '/inc/post.php' ); ?>
<?php endwhile;
else : ?>
<h2><span>Page Not Found</span></h2>
<?php endif; ?>
<?php include (TEMPLATEPATH . '/inc/nav.php' ); ?>
</div><!-- main -->
<?php get_sidebar(); ?>
<?php get_footer(); ?>
A list of search results containing the_search_query
.
page-blog.php
<?php /* Template Name: Blog */ ?>
<?php get_header(); ?>
</header>
<div id="content">
<div class="main">
<h2><span><?php the_title(); ?></span></h2>
<?php query_posts( 'posts_per_page=5' ); ?>
<?php if ( have_posts() ) while ( have_posts() ) : the_post(); ?>
<div class="post-page-blog">
<?php if(has_post_thumbnail()) { ?>
<a href="<?php the_permalink() ?>"><?php the_post_thumbnail( array(50,50) ); ?></a>
<?php } else {
echo '<img src="'.get_bloginfo("template_url").'/css/img/no-img-50x50.gif" />';
} ?>
<h3 id="post-<?php the_ID(); ?>"><a href="<?php the_permalink() ?>" rel="bookmark" title="Permanent Link to <?php the_title(); ?>"><?php the_title(); ?></a></h3>
<p><?php comments_number('No Comments', '1 Comment', '% Comments'); ?></p>
</div><!-- .post -->
<?php endwhile; ?>
</div><!-- main -->
<?php get_sidebar(); ?>
<?php get_footer(); ?>
The code comment in the first line is a WordPress convention for custom page templates. You need to declare a template name which will show in your dashboard when creating a new page or editing an existing one.
DRY: Don’t Repeat Yourself
Repetition in theme development can be spot quite often if you’re not careful. Most of the times, the post code will repeat on the index, single, archive and search files.
Dan Harper made an awesome screencast some time ago, applying the DRY programming technique into a WordPress theme, using includes
as we did with the post, navigation and featured area. But, luckily for us, since version 3.0, WordPress is shipping with the get_template_part
function to achieve just that.
Any of these two techniques will do, it’s up to you: the WordPress way or the Rails DRY way.
More CSS styles and testing
I’m not showing the final CSS file here because of its length. Yep, our CSS has grown exponentially and has been changed with new WordPress classes and ID’s. You have to think ahead to the future of our theme users need. They will surely drop a widget on your sidebar or footer and your CSS has to be ready. Sometimes users are HTML-savvy and they will write that rare tag you never thought of and BUM!, your design fails.
Go ahead and download the official WP-theme unit test. It has HTML elements and all sort of crazy stuff to style before hand.
Thanks!
Once again, download the Brilliante Layout theme and take it for a ride. Feel free to use and abuse.
Related Topics
Top