Specify Unique CSS File Per Post
I’m a HUGE fan of being able to link up a CSS file on a per-page basis. I just find it extremely common that a page needs CSS styling unique to it, and I hate litering a sites main stylesheet with customizations that only one particular page needs. We’ve talked about this before, and even created a custom method for doing so, as well as mentioned the art direction plugin, which makes this easily possible.
Both of those methods are still sweet, but I find in general that I like to write my CSS in actual CSS files I keep in my theme and can edit using whatever CSS editor I like, as well as keep things clean and simple in the admin. So, I’ve altered the technique a bit.
The idea is to add a simple text input to the page editor, right underneath the content area, where you can manually specify the name of a CSS file. In the image below, you can see I’ve also added one for JavaScript.
To add this to your site, it’s just some code to add to the functions.php file. Should work for any site.
// Custom Stylesheet box in Pages/Posts
add_action('admin_menu', 'digwp_custom_css_hooks');
add_action('save_post', 'digwp_save_custom_css');
add_action('wp_head','digwp_insert_custom_css');
function digwp_custom_css_hooks() {
add_meta_box('custom_css', 'Name of custom CSS file', 'digwp_custom_css_input', 'post', 'normal', 'high');
add_meta_box('custom_css', 'Name of custom CSS file', 'digwp_custom_css_input', 'page', 'normal', 'high');
}
function digwp_custom_css_input() {
global $post;
echo '<input type="hidden" name="custom_css_noncename" id="custom_css_noncename" value="'.wp_create_nonce('custom-css').'" />';
echo '<input type="text" name="custom_css" id="custom_css" style="width:100%;" value="'.get_post_meta($post->ID,'_custom_css',true).'" />';
}
function digwp_save_custom_css($post_id) {
if (!wp_verify_nonce($_POST['custom_css_noncename'], 'custom-css')) return $post_id;
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return $post_id;
$custom_css = $_POST['custom_css'];
update_post_meta($post_id, '_custom_css', $custom_css);
}
function digwp_insert_custom_css() {
if (is_page() || is_single()) {
if (have_posts()) : while (have_posts()) : the_post();
$filename = get_post_meta(get_the_ID(), '_custom_css', true);
if ($filename) {
echo "<link rel='stylesheet' type='text/css' href='" . get_bloginfo('template_url') . "/css/" . $filename . "' />";
}
endwhile; endif;
rewind_posts();
}
}
If you are happy with just CSS only, you can stop there. If you would like the JavaScript box as well, here is that code.
// Custom JavaScript in Pages/Posts (...lots of repeated code, could be better)
add_action('admin_menu', 'digwp_custom_js_hooks');
add_action('save_post', 'digwp_save_custom_js');
add_action('wp_head','digwp_insert_custom_js');
function digwp_custom_js_hooks() {
add_meta_box('custom_js', 'Name of custom JavaScript file', 'digwp_custom_js_input', 'post', 'normal', 'high');
add_meta_box('custom_js', 'Name of custom JavaScript file', 'digwp_custom_js_input', 'page', 'normal', 'high');
}
function digwp_custom_js_input() {
global $post;
echo '<input type="hidden" name="custom_js_noncename" id="custom_js_noncename" value="'.wp_create_nonce('custom-js').'" />';
echo '<input type="text" name="custom_js" id="custom_js" style="width:100%;" value="'.get_post_meta($post->ID,'_custom_js',true).'" />';
}
function digwp_save_custom_js($post_id) {
if (!wp_verify_nonce($_POST['custom_js_noncename'], 'custom-js')) return $post_id;
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return $post_id;
$custom_js = $_POST['custom_js'];
update_post_meta($post_id, '_custom_js', $custom_js);
}
function digwp_insert_custom_js() {
if (is_page() || is_single()) {
if (have_posts()) : while (have_posts()) : the_post();
$filename = get_post_meta(get_the_ID(), '_custom_js', true);
if ($filename) {
echo "<script type='text/javascript' src='" . get_bloginfo('template_url') . "/js/" . $filename . "' ></script>";
}
endwhile; endif;
rewind_posts();
}
}
You could probably combine these together a bit better than I have done here, it’s a bit redundant.
How it Works
There are three things going on here, which tap into three existing WordPress “hooks”.
- Adds text inputs to the page editing screen of the admin using the
admin_menu
hook and theadd_meta_box
function - Save the values of those text inputs when the page is saved using the
save_post
hook - Output
<link>
or<script>
tags in the<head>
of the site (when those values are present) using thewp_head
hook.
The Result
If you put the value “yeahbaby.css” in that input box, when that particular page loads, you are going to get this output in the <head>
:
<link rel='stylesheet' type='text/css' href='http://yoursite.com/notes/wp-content/themes/your-theme/css/yeahbaby.css' />
“yoursite.com” will obviously be your sites domain, and your-theme will the folder in which your currently active theme resides. Do keep that in mind, as should you change themes, you’ll need to move the “css” folder in that theme that contains these custom CSS files to the new theme.
UPDATE (May 13, 2010)
Notice how all the function names are prefixed by digwp_
– that is in accordance to best practices described by Andrew Nacin here.
This has also been made into a plugin by Utkarsh Kukreti. Requires PHP5+.
Ways This Code Could Be Improved
I was holding back on publishing this because there are a lot of ways it could be better. But I had mentioned it at the CMS Expo and there was some interested and I said I would, so I’m releasing it a bit early. Here are some things that I think could be better…
- Combine the functions together to create less redundancies between the CSS and JavaScript versions.
- Allow for a comma-separated list in case multiple CSS files are needed.
- Make it a plugin instead of custom functions. Add options screen to specify file path to custom CSS folder.
21 responses
-
Thank you so much for this code, I was trying to find something similar, the great thing here is you can expand the code to make more of it :)
I will give it a try soon on one of my projects
-
I’m with Hesham. I’m learning quickly (thanks for that as well), but this one was bugging me. I’ve used the Art Direction plugin, but I hate to copy/paste code, and wanted to just find a way around it.
Thanks to you both for keeping this thing going. It’s a ton of help.
-
Chris Coyier… how are you always the shit?! Thank you sir.
-
Great explanation. I’ve googled my eyes out for a decent write-up of how to add custom meta boxes to the admin panels, and how to use the data in the templates. Though I hate writing HTML in PHP’s
echo()
method, aninclude
will do it for longer pieces.-
Johan, you can see this article to create meta boxes more easily and flexibly.
-
-
Great idea. I’d like to adapt this code to make the CSS change based on categories or tags.
I’ve got a new project! ;P
-
Actually, no offense but I find it better to use an extended body class function for example this is what I do with ComicPress:
add_filter('body_class','comicpress_body_class'); function comicpress_body_class($classes = '') { global $current_user, $is_lynx, $is_gecko, $is_IE, $is_opera, $is_NS4, $is_safari, $is_chrome, $is_iphone, $post, $wp_query, $comicpress_options; if (!empty($current_user)) { $user_login = addslashes($current_user->user_login); if (!empty($user_login)) $classes[] = 'user-'.$user_login; } else { $classes[] = 'user-guest'; } if (function_exists('comicpress_is_member')) { if (comicpress_is_member()) { $classes[] = 'sitemember'; } else { $classes[] = 'non-sitemember'; } } if (is_single() && !is_attachment()) { if (in_comic_category()) { $classes[] = 'comic'; } else { $classes[] = 'noncomic'; } } if($is_lynx) $classes[] = 'lynx'; elseif($is_gecko) $classes[] = 'gecko'; elseif($is_opera) $classes[] = 'opera'; elseif($is_NS4) $classes[] = 'ns4'; elseif($is_safari) $classes[] = 'safari'; elseif($is_chrome) $classes[] = 'chrome'; elseif($is_IE) $classes[] = 'ie'; else $classes[] = 'unknown'; if($is_iphone) $classes[] = 'iphone'; // Hijacked from the hybrid theme, http://themehybrid.com/ if (is_single()) { foreach ( (array)get_the_category( $wp_query->post->ID ) as $cat ) : $classes[] = 'single-category-' . sanitize_html_class( $cat->slug, $cat->term_id ); endforeach; $classes[] = 'single-author-' . get_the_author_meta( 'user_nicename', $wp_query->post->post_author ); } if ( is_sticky( $wp_query->post->ID ) ) { $classes[] = 'sticky-post'; } // NOT hijacked from anything, doi! people should do this. $rightnow = date('Gi'); $ampm = date('a'); $classes[] = $ampm; if ((int)$rightnow > 559 && (int)$rightnow < 1800) $classes[] = 'day'; if ((int)$rightnow < 600 || (int)$rightnow > 1759) $classes[] = 'night'; if ((int)$rightnow > 2329 || (int)$rightnow < 0030) $classes[] = 'midnight'; if ((int)$rightnow > 0559 && (int)$rightnow < 1130) $classes[] = 'morning'; if ((int)$rightnow > 1129 && (int)$rightnow < 1230) $classes[] = 'noon'; if ((int)$rightnow > 1759 && (int)$rightnow < 2330) $classes[] = 'evening'; $classes[] = strtolower(date('D')); if ( is_attachment() ) { $classes[] = 'attachment attachment-' . $wp_query->post->ID; $mime_type = explode( '/', get_post_mime_type() ); foreach ( $mime_type as $type ) : $classes[] = 'attachment-' . $type; endforeach; } $classes[] = 'layout-'.$comicpress_options['cp_theme_layout']; return $classes; }
This way when doing styling you can not only change your CSS for each and every page you have every variation in between, for example
body.home { background: #000; }
Will change the background for the home and:
body.page-id-3434 { background: #ccc; }
Will change something specifically for that page.
.category-random-thoughts .post { background: #999; }
You can specify categories like that.
Even more you can do with extended body classes as noted in the code above you can set the time of the day even week to change the site design.
-
Great, thanks for this.!!
Sorry if I missed this, but does this add a custom style sheet in addition to the theme’s style.css, so that yeahbaby.css is called after style.css (thus overriding it, right?) in the header — or does it replace it, so that yeahbaby.css must include the styles of the default stylesheet, plus additions and changes? I’d prefer both to be called and yeahbaby.css to overwrite the default style.css.
BTW, are you essentially endorsing Kerrick’s approach over Hassan’s from Feb 8? I read you going the other way on that in that post.
-
No no no! Don’t echo out the script/css tags as text. Enqueue them so that other plugins (such as caching or minifying plugins) can process them. The functions you need are wp_enqueue_script() and wp_enqueue_style().
-
You need to do those in the init btw:
add_action('init','your_load_scripts_function');
-
They should be added to
wp_print_scripts
andwp_print_styles
respectively.
-
-
-
Looks useful, BUT, good designs and code can be globally used, realy.
-
Oh, I don’t know. Check out Jason Santa Maria’s articles. He does different designs for different posts whenever he wants to.
-
-
A global CSS gets loaded at first and then any class call from any page would take it from the cached CSS file. This makes navigation in the site more responsive than loading a CSS file with each page.
-
web browsers which could do no more. Well, now we have browsers that can do much more. Yep, there are problems, but there is a Chinese proverb which says something to the effect of, “Progress consists of trading new problems for old ones.”
-
Was looking for something like this. Thanks
-
I love this.
In practice, when I tried it on a post, I realized that when that post is showing up in other pages–such as the main blog loop, or I suppose archive pages, etc., it will not have those styles.
(I’m sure in some cases, this is exactly what you would want.)
Anyway, it would be cool to create a way where it would apply the stylesheet whenever that post showed up, even in one of the other pages.
Hmm, I suppose that takes a lot of logic because you need to load the stylesheets in the head, so you’d need to kind of go through the loop when writing out the head, and that would be odd. I guess in this case, it would make more sense to use a different approach.
-
Thanks for sharing this! A valuable reference. I also appreciate the tip and link on creating a custom prefix. I am checking out WP 3.0 RC1 and trying to be more WP savvy.