March 15, 2004

Style Switcher Implementation

There seems to be a few requests regarding to how the style switcher is implemented, so this entry will serve this purpose - detailing how I implemented the style switcher on the Movable Style site, including samples of source code that you can use for your own site.

The internals of this style switcher is actually quite simple, but it requires PHP running on the server side for it to work. Here are some objectives that I had when I first started:

  • Minimal client side (java-)scripting.
  • Works on every page.

Setting Up The Tree

Here's the directory structure on the webserver that I have set up for the Movable Style site. There is no database behind the styles, and everything is stored as files in directories. So all the scripts on this page depend on this set up.

  • / - Base directory for
    • archives/ - Blog entry archives
    • inc/ - PHP includes
    • styles/ - Styles archives
      • Boxed/
        • styles-site.css
      • Boxed_Green
      • Lovingrey
      • Mac_Stripe
      • ...
      • Slashdot
      • TypePad_Thin

I put all my styles into the styles/ directory, with individual style directories created inside them. In each style directory, you should always find at least the styles-site.css, which provides the CSS. For some there are also image files used by the style. If there are more than just the .css, I also created a ZIP archive titled which contains all the files in that directory. For example:

$ ls styles/Slashdot
bannerbg.png  headerbg.png  styles-site.css
$ unzip -t styles/Slashdot/
Archive:  styles/Slashdot/
    testing: bannerbg.png             OK
    testing: headerbg.png             OK
    testing: styles-site.css          OK
No errors detected in compressed data of styles/Slashdot/

PHP Auto Prepend

I am too lazy to add a <?php import(...); ?> on every template that I have touched, so I settled for the PHP auto prepend. Here is what I have in my .htaccess in the base directory.

php_value include_path [Movable Style base directory]/inc
php_value auto_prepend_file "prepend.php"

PHP Source Code

Here is the content of prepend.php which provides functions to work out the stylesheet to use.

// Where the script can find the list of style directories.
define(STYLE_BASE,    '[Movable Style base directory]/styles/');
// What the default style should be.
define(DEFAULT_STYLE, 'MovableType_Clean');
$style = $_GET['style'];
if (!$style) {
    $style = $_COOKIE['style'];
} else {
    setcookie('style', $style, time()+31536000, '/');
function get_style() {
    global $style;
    if (is_valid_style($style)) {
        return $style;
    } else {
        return DEFAULT_STYLE;
function get_style_title() {
    $s = get_style();
    $s = str_replace('_', ' ', $s);
    return $s;
function get_style_list() {
    $result = array();
    if ($d = @opendir(STYLE_BASE)) {
        while ($f = readdir($d)) {
            if (is_valid_style($f) && !is_hidden_style($f)) {
                $result[] = $f;
    return $result;
function is_hidden_style($f) {
    return file_exists(STYLE_BASE."$f/hidden");
function is_valid_style($f) {
    return file_exists(STYLE_BASE."$f/styles-site.css");
function list_style() {
    $list = get_style_list();
    $curr = get_style();
    $html = '';
    foreach ($list as $r) {
        $r2 = str_replace('_', ' ', $r);
        $selected = ($r == $curr) ? ' selected="selected"' : '';
        $html .= "<option value=\"$r\"$selected>$r2</option>";
    return $html;

It would take hints on what the current style should be, based on:

  • style parameter passed in through GET query string, i.e. in URL where ?style=FooBar.
  • style cookie so that current style setting can be retained across pages.
  • If neither sources above provide a valid style, then DEFAULT_STYLE will be used.

Movable Type Template Modification

Now we need to modify the Movable Type templates to use functions provided by PHP to obtain the current style.

First of all, all generated files need to be processed by PHP. That would mean the file extension should all be changed to .php, or somehow configure the Apache to filter through the default .html with PHP.

Now use the following code in the HTML head of the index/archive pages so that the stylesheet will be selected based on query string/cookie.

<link rel="stylesheet" href="<$MTBlogURL$>styles/<?=get_style()?>/styles-site.css"
      title="<?=get_style_title()?>" type="text/css" />

If you want a drop down list for a style switcher, here is an example, as implemented on the Movable Style site.

<div>Select a stylesheet to change to.</div>
<form action="index.php" method="get">
  <select name="style" onchange="this.form.submit()">
    <?= list_style() ?>

Comment, Trackback & Search

In Movable Type, comments and trackback that brings up pop-up window are dynamically generated inside the CGI, so that PHP derivatives would not work in these templates. What I did was writing a simple MT plugin to determine the style name base on value stored in cookie. Note that in this case, MT must be installed under the same host as the blogsite in order to share the cookie, Otherwise you might need to play around cookie's domain in PHP so that CGI's in MT can see the value.

Now, put the following content into a file called mt/plugins/

use strict;
use MT::Template::Context;
use CGI::Cookie;
MT::Template::Context->add_tag(MovableStyle =>
    sub {
        my %cookies = fetch CGI::Cookie;
        if ($cookies{'style'}) {
            return $cookies{'style'}->value;
        } else {
            return 'MovableType_Clean';

And modify your comment/traceback/search templates with the following HTML code.

<link rel="stylesheet" href="<$MTBlogURL$>styles/<$MTMovableStyle$>/styles-site.css"
      type="text/css" />

Done! Pretty simple, isn't it? :)

Posted by Dasme at March 15, 2004 10:40 AM | TrackBack

You seem to have a glitch in your PHP. On the right side, when you click "download style" it mangles the url:

See, the SITE_BASE shouldn't be there. I'd bet it's missing some kind of prefix that makes PHP interpret it as a variable.

Nice work with the site, anyhoo.

Posted by: Rafiki at March 16, 2004 07:34 AM

Thanks for pointing that out.

I was cleaning up the script yesterday to make this blog entry, but did not realise that I have broken something.

Posted by: scotty at March 16, 2004 07:55 AM

Warning: Cannot modify header information - headers already sent by (output started at /home/hildyard/public_html/mike/inc/prepend.php:1) in /home/hildyard/public_html/mike/inc/prepend.php on line 12

i get that error. am i misconfiguring???
the dropdown can SEE the folders, but it wont impliment the new stylesheet.

Posted by: the mad kiwi at March 17, 2004 10:46 AM

Hey, first I want to say that this is a great thing for a new to programming/scripting like me. I successfully implemented on a test blog ( ) and am going to switch it to my main domain (

But I have a question. When I skin my self, I like to do really radical changes, and to do that, sometimes I would need to use a new html index. Is there a way to hack this or configure it so that, when I switch styles, I can switch my html file too? (without having to link to several different "indexes")

I tried the method at , but I could get it to work. (


Posted by: LaLindsey at March 21, 2004 05:14 PM

The 'typepad thin' style suffers from the "peekaboo bug". I used the style, clicked on the 'comments' at my site, and the comment came up empty until I scrolled around.

There's more information on the IE6 peekaboo bug at the movable type forums, but I can't find the post at the moment. Sorry.

Posted by: Matt at March 26, 2004 02:26 AM

Thanks Matt!

It seems to be the cause of problem for a few of my stylesheets, due to my lack of testing under IE6. I would look into it and try to fix them later.

Posted by: scotty at March 26, 2004 10:20 AM

Kiwi's problem, above, is the same thing I'm getting.

(Warning: Cannot modify header information - headers already sent by - etc, etc...)

What's cause/fix on that? (I'm also trying to find a contact email address for kiwi to ask HIM...)

Posted by: Karl Elvis at March 28, 2004 02:45 AM

Kiwi & Karl,

Make sure that there is no space or empty line before "<?php" in the prepend.php file, i.e it should be the very first character in the file.

Posted by: scotty at March 28, 2004 06:18 AM

Rock *on* scott! That did it.

Thanks man!

Posted by: Karl Elvis at March 28, 2004 07:17 AM

That was an interesting read! Thanks!

However, with my few technical skills I tried it on a test MT blog and failed.
The .../ ?=get_style()? /styles-site.css stays untouched instead of being processed by php. Though I changed php in my preferences/templates of my blog, and I'm certain php works on the server (phpinfo.php). I know my hosting company Dreamhost, works with php-cgi, but does that change anything?? Any ideas?

Posted by: Rippling at March 28, 2004 11:26 AM


Did you also change the index file from index.html to index.php? The very extension of the file determines which handler Apache will filter them through.

Posted by: scotty at March 29, 2004 12:14 AM

I implemented the stylesheet switcher and all is working well. I tweaked the styles a bit, but that's just a personal preference thing.

One suggested changed in the instructions. Make it clear that the paths in the PHP code should be full paths, not URL paths, e.g. /var/www/blog instead of /blog. I figured it out immediately, but a non-techie might be confused.

Now to find some more styles to play with.

Posted by: Rossz at March 29, 2004 06:21 AM

I am not a server owner, I have my site hosted on a provider's server. would I be able to modify .htaccess file? If not, is your style switching solution workable for me?


Posted by: olufsen at April 6, 2004 05:38 AM


Yes. Most hosts allow you to modify (or upload) .htaccess to your website.

Posted by: scotty at April 6, 2004 06:36 AM

Warning: Unknown(prepend.php): failed to open stream: No such file or directory in Unknown on line 0

Warning: (null)(): Failed opening 'prepend.php' for inclusion (include_path='[]/inc') in Unknown on line 0

I don't get what to put in the include path, can anyone helpp me?

Posted by: Richie at April 7, 2004 10:53 PM

Thanks for the reply, and sorry for my late one - I had to unexpectedly leave for 10 days.

I did change the index.html into index.php as mentioned in the guideline. Still, not working.. :( I'll try another time though, and see if I can come with more concrete questions. Thanks anyway!

Posted by: Rippling at April 8, 2004 03:37 AM


You would want to set the un*x path of your blog directory into include_path, instead of your web address. If you don't know the path, you might want to upload a php file (say, getcwd.php) that contains this line:

which would then reveal the absolute path of PHP's current working directory.

Posted by: scotty at April 8, 2004 08:14 AM

Thanks, but now I think I know the path. It's like /home2/jusano/public_html/ but then I get the message
Warning: Unknown(prepend.php): failed to open stream: No such file or directory in Unknown on line 0

Warning: (null)(): Failed opening 'prepend.php' for inclusion (include_path='/home2/jusano/public_html/inc') in Unknown on line 0

What am I supposed to do? Can you give me an example, please. Anyone? Just one example of anyone's .htaccess's code?

Posted by: Richie at April 8, 2004 08:57 AM


You do have '' in the "/home2/jusano/public_html/inc" directory, don't you?

Otherwise, sometimes some sites forbid changing PHP options in .htaccess. In that case, you might want to use the full path at the beginning of all your Movable Type templates (index + archive). For example, put this on the very first line:

And then remove "auto_prepend_file" from your .htaccess.

Sometimes hosting can be troublesome, as different hosting provider has different configuration. Pitty that I can only provide a "guideline" or "direction" on how it is implemented, instead of something that you can just copy and paste. I am running this site on a box that I have full control of, so some of my approaches might be considered a "hack" instead of something generic. :)


Posted by: scotty at April 8, 2004 09:28 AM

o no. I just had prepend.php instead

Posted by: Richie at April 8, 2004 11:32 AM

Please check my site,! ARGHHHH!!! Look at the little drop down menu, why is it blank? I did everything you have in the tutorial

Posted by: Richie at April 8, 2004 12:21 PM

I fixed it now, but now my layouts won't change!

Posted by: Richie at April 8, 2004 11:20 PM

Thanks Scotty. Everything's working as planned. But there's one big catch. It's so wrong, I don't know what to do. Any other folder that you have other than MT that uses PHP or MySQL won't operate properly. See, before I did this, worked perfectly. Now it says
Warning: Cannot modify header information - headers already sent by (output started at /home2/jusano/public_html/inc/ in /home2/jusano/public_html/ads/index.php on line 22

what do I do ?

Posted by: Richie at April 14, 2004 09:27 PM

Thanks, it works. Now, what file am I supposed to have,, prepend.php, or both?

Posted by: Richie at April 14, 2004 10:11 PM

Hi. I discovered this handy site the other day and have followed the instructions step-by step. Except for the default MT stylesheet, the pages appear not so good for me in Safari. Oh well, your switching code is simple and effective. Perhaps I have a typo. I can't see it. =(

Posted by: Ryan at April 24, 2004 07:03 PM

Has anyone found a way to stay compliant with the switcher. All the pages here seem to create an error (including mine)....

Line 683, column 112: value of attribute "SELECTED" cannot be "1"; must be one of "SELECTED"

...>Original<option value="Slash_

Anyone know of a fix around this. Great code...Quite fun!

Posted by: Lee at May 18, 2004 02:49 PM

Yeah. Just do what the validator say. I was getting lazy so I did not check against a validator. I'll update the document soon. It should be

<option selected="selected">

Posted by: scotty at May 18, 2004 03:12 PM

Thanks for the help. Works like a champ!!!

Posted by: Lee at May 19, 2004 12:03 AM

This worked great. the only change I had to make was to the get_style_list function in prepend.php to filter the current, and parent directories.

if ($f != '.' && $f != "..") {
if (is_valid_style($f) && !is_hidden_style($f)) {
$result[] = $f;

Posted by: Kenny Gatdula at June 17, 2004 10:36 PM
Post a comment

Remember personal info?