Build your own PHP style sheet switcher

Want to have a style switcher that lets your site’s visitors choose a different style sheet? Want it to work even if there is no JavaScript support? The trick is to use a server-side language like PHP, which is what I used to use for my style switcher.

Note: I have now taken the style switcher offline since I kept forgetting to maintain multiple stylesheets. It’s not because having a style switcher is bad or anything.

Using PHP to let the user switch to a different CSS file is nothing new. But it is one of the things that I am often asked about, so I thought it would be good to have a write-up to refer people to in the future.

As I mentioned this will work when JavaScript is disabled. It will not work when cookies are disabled, however. Nothing bad will happen, but it simply won’t work and the user will be returned to the same page they were on. Just to make you aware of that right from the start.

I use this style switcher to let visitors switch between two different layouts (“Zoom” and “Normal”) here on 456 Berea Street. If you want to let the user choose between more than two stylesheets you will need to come up with a different solution.

I wouldn’t call myself a PHP expert by any means, so if you think my code could be improved in any way, please let me know.

To switch styles, all you need to do is activate the link to the styleswitcher. As you may have noticed if you have tried that, the URL that invokes the styleswitcher is always the same. The link leads to a php file that looks like this:

  1. <?php
  2. $layout = (isset($_COOKIE['layout']) && ($_COOKIE['layout'] == "zoom")) ? "main" : "zoom";
  3. setcookie("layout", $layout, time()+31536000, '/');
  4. $ref = (isset($_SERVER['HTTP_REFERER'])) ? $_SERVER['HTTP_REFERER'] : "{$_SERVER['SERVER_NAME']}/";
  5. header("Location: $ref");
  6. ?>

Here’s what happens, line by line:

  • Line 2: If the layout cookie exists (the browser has been here before) and has the value “zoom” (the user has switched to Zoom layout), $layout is set to “main”. Otherwise $layout is set to “zoom” (the browser doesn’t accept cookies, the style switcher has not been used with this browser, or the user has switched to Normal layout). Note that “main” and “zoom” are the file names (except for the .css extension) used for the respective styles. Change these values to match your file names.
  • Line 3: The value of $layout is stored in a cookie.
  • Line 4: If a referer header exists, its value is stored in $ref. If it doesn’t exist, $ref is set to “{$_SERVER[‘SERVER_NAME’]}/” as a backup to redirect the browser to the home page.
  • Line 5: The browser is redirected to the URL stored in $ref.

On all pages a check is performed to decide which stylesheet to load:

  1. <?php
  2. $layout = (isset($_COOKIE['layout']) && ($_COOKIE['layout'] == "zoom")) ? "zoom" : "main";
  3. ?>

If a cookie exists and its value is “zoom”, $layout is set to “zoom”. Otherwise, $layout is set to “main”. At this point $layout contains the name of the CSS file that should be loaded.

Finally, when loading the main style sheet the value stored in $layout is inserted instead of the file name in the @import path:

  1. <style type="text/css" media="screen,projection">
  2. @import '/css/<?php echo $layout; ?>.css';
  3. </style>

And that’s it. Use it, spread it, improve it.

Posted on August 23, 2006 in CSS, PHP

  • Previous post: Light text on dark background vs. readability
  • Next post: Selecting country names in forms

Comments

  1. August 23, 2006 by Nate K

    I think this looks good Roger. It only takes basic PHP to get something like this in effect. I did this on a website a while back and it works fine, especially for return visits. When I did it I used $SESSION instead of $COOKIE in case a user was on a machine other than their own (library, school, work, etc). This way the switcher would end once the browser session opened and the next visitor would see the main display. I would implement $_COOKIE if i was using an ACL of sorts to let users select for each return visit/login.

    Im sure this will help others implement a style switcher for their site.

  2. August 23, 2006 by Sam

    I’m not a PHP expert but I think Nate is right regading the using of $SESSION. Very often the cookies are blocked or disabled. What should one do in this case? It means that the switcher won’t work then. Maybe I’m wrong.

  3. August 23, 2006 by niklas

    Hmm.. I must say it would be nicer starting a session, there’s a lot of people out there who don’t want cookies… Or perhaps a function to check for cookie support and then act accordingly?

  4. August 23, 2006 by Roger Johansson (Author comment)

    I’m not using sessions because I figure those who switch would find it annoying to have to do it every time they visit the site. Besides, unless I am mistaken sessions also require cookies unless you choose to add the unique visitor id to the URL:

    A visitor accessing your web site is assigned a unique id, the so-called session id. This is either stored in a cookie on the user side or is propagated in the URL.

    PHP Session Handling Functions

    Checking for missing cookie support and handling it in a different way than just silently failing sounds like a good improvement.

  5. August 23, 2006 by nick

    but if you accept the cookie the stylesheet of your choice will be used the next time you visit the site, while the session data will be gone.

    session variables and cookies just work differently! substitute one for the other and it’s not the same thing…

  6. August 23, 2006 by Nate K

    Roger, Users can choose to turn cookies off in their browser (I know you know this, just re-stating). If they were to do this, or at a facility that doesn’t allow cookies for some reason, they would not be able to use your style switcher. Using $_SESSION on the other hand will always work, as it will assign the unique ID via HTTP headers - and doesn’t require that you propogate the ID via your URLS. Obviously, since this is stored on the server side - it will expire when the browser is closed. Using $_COOKIE will store the same data, only in the browser.

    This is why, when I used this, I gave registered users the option of using a cookie and being ‘remembered’ on subsequent visitors. For those who were first time or just browsing, I set the cookie via $_SESSION, which requires nothing on the browser end. You can try this with any site (telnet if you would like). You can turn off cookies in your browser, and $_SESSION will still work on your site.

  7. August 23, 2006 by Gerben

    As far as I know sessions use cookies to store the sessionId.

    A very nice and simple implementation of a stylesheet switcher. The only minor problem with this approach is that you can’t have the html be cached by the browsers.

  8. August 23, 2006 by Nate K

    Roger, I do stand corrected (not sure what I was thinking), session cookies are propagated via HTTP Headers (Set-Cookie). Most of the times this is in the form of PHP=**; Path=/ - so if they choose to eliminate ALL cookies, then this would be removed. COOKIE, on the other hand, stores its information in the browser for subsequent visits.

    So, it is a preference thing. Thinking of the many ways people may venture to a site, I would use SESSION, so subsequent visitors will see your original styles. Having worked in different IT departments, each one has their quirks with security. Some would not allow Javascript and Cookies, others allow them freely. Thinking of the public sector, you can never be sure what they have on their machines. So - it is a conveneince if they WANT to see the same styles with each return. But what about a public computer that could have 10 different people visit via the same machine? Not a big deal - but something to think about nonetheless.

  9. August 23, 2006 by Emil Stenström

    @Gerben: In fact you could make the html cacheable if you combined it with my dynamic and static CSS combined technique.

    Nice and simple solution Roger, just like I want them.

  10. August 23, 2006 by John Beales

    Ideally you can combine the $COOKIE and $SESSION techniques and live in the best of both worlds. This is because, (as discussed above), the session identifier will be passed via the URL if cookies are disabled.

    When a visitor first arrives at the site check to see if they have a cooke set from a previous visit. If so set the $_SESSION variable to the appropriate stylesheet. If no cookie is found use whatever you decide is your default.

    When a visitor clicks the styleswitcher link switch both the cookie and session variable to the new stylesheet.

    This way visitors from schools, libraries, and other places with cookies turned off still can use the alternate stylesheet for the duration of their visits and folks who have cookies enabled won’t have to switch to their preferred stylesheet every time they visit the site.

    The code will be slightly more complicated. The first part of the solution will have to be in the code that goes on every page and session_start(); will have to be called at the beginning of every page but it’s not too bad and you get the best of both worlds.

  11. August 23, 2006 by Roger Johansson (Author comment)

    Gerben, Emil: I’m not sure I follow you. Why can’t the html be cached?

    John: Hmm. I’ll take a look at combining $_COOKIE and $_SESSION. Not sure I want to have session info in my URLs though.

  12. August 23, 2006 by Chris
    php_value session.use_trans_sid "off"
    

    in the .htaccess file should strip the ?PHPSESSID=number part from the URL.

  13. August 23, 2006 by dawn

    I think it’s important to consider who’s reading, where they are coming from and why they are switching the stylesheet as well as what the purpose of the site is.

    I could see a site like msn.com using sessions rather than cookies because a much higher percentage of msn’s users use shared computer terminals than readers of a site like 456 where the majority of the regular readers would have their own computers (I may just be making crazy assumptions here). Along those lines, for the users on computer terminals it makes sense to use sessions because the chances that they will be back on that exact same computer the next time they visit the site are relatively low (depending on where they live, of course) so they’d likely have to switch the style next time they visit anyways.

    Personally if I had to switch a stylesheet every time i went to a site I probably would stop bothering switching. If it was a case of the switched sheet aiding my use of the site (a la increased font size etc) then I’d probably just stop reading.

  14. August 24, 2006 by Nate K

    Roger, use the information Chris gave you above - this will prevent them from being propagated to the URL. I use that setting on my sites, as well as auto starting the session and changing the SID.

    Good info Chris, you beat me to the punch. he.

  15. August 24, 2006 by Paulo

    I’ve been running a “randomizing style sheets” gimmick by serving up a .css.php file (with a header of Content-type: text/css, of course) which counts “skin” files in a style folder, then randomly imports one every reload. No cookies, though — apparently my visitors prefer the surprise.

  16. August 24, 2006 by Steve Williams

    Roger, rather than a lengthy session id in the urls, you could define a session variable and tie that to a simple url parameter via GET (in my case ‘id’) - I did it about a year ago and whilst it won’t persist page to page without cookies, you can at least switch any page you reach, and the php is ridiculously simple :)

    Under normal cookie situations, beyond the initial page you switch the parameter is not needed and the urls are clean.

    Your post has got me thinking that there must be a way to fall back to the short url parameter in the event of no cookie support, rather than an ugly session id, I’ll have a play when I get time…

  17. August 24, 2006 by Harmen Janssen

    I use a similar approach on my website. Only problem here, naturally, is that users with cookies disabled can’t have the benefits of your “zoom”-layout.

    This might not be a problem for regular visitors, who understand this and might consider allowing cookies from your domain, but one-time visitors, who only read, say, one or two articles could get irritated and don’t come back.

    By the way, I read about storing session id’s in a mySQL database, that should solve the cookie problem when using sessions shouldn’t it? Extend this method using a cron-job that cleans up the database every week or so, and you can use the technique mentioned above about combining cookies and sessions.

    (disclaimer: I might have understood wrong, having never used this technique myself)

  18. August 24, 2006 by Nate K

    RE: Harmen Storing them in a database wont solve any problem that he is having. You can store your sessions an array of different ways - file, user, or database. These methods all handle sessions the same way - so whether its a file or database it all works the same way (even if you use an abstraction layer).

    Storing them in a database solves the problems of hiding sessions on shared hosting environments, and you don’t need a cron job to clean them out. If you override the default session handler and use your database, you simply have to define a garbage cleanup method in your class and it will handle everything for you.

    So - that really doesn’t solve the problem at hand, no matter where you bury the sessions.

  19. August 24, 2006 by Sébastien Guillon

    Roger,

    I use a similar routine as the one described by John Beales, and I also set use_trans_sid to ‘off’ to keep my URLs clean.

    The one thing I think is missing here is the proper HTML.

    Why don’t you provide the alternate stylesheet mechanism via link elements in the header?

    Of course it should reflet the state of your style switcher (each stylesheet link having the rel attribute set to ‘stylesheet’ or ‘alternate stylesheet’ according to its current state) but that’s easily done in PHP.

    I always like to look at alternate stylesheets using my browser controls, it’s a quick way to see what awaits me. I know changes made this way are not persistent but I still think it’s neat to provide HTML information when possible (the semantics for a style switcher are pretty straightforward).

    Note: my site is content negociated so there are 2 different ways of including stylesheets whether the site is serrved as text/html or as application/xhtml+xml.

  20. August 24, 2006 by John Beales

    Hmmm. I believe what Chris is doing in Comment #12 and Sébastien in #19 will make it so that your session will not propogate between pages for users with cookies disabled. I just took a quick read through some of the session handling info in the PHP manual. The whole point of the usetranssid is to allow PHP do do the work of putting a session ID in your links for you, thus allowing non-cookie-using users to have the pleasure of sessions.

    Be aware however of this security issue that I learned about while checking my facts for this comment.

  21. August 24, 2006 by Mike Cherim

    Ah, you beat me to the punch, Roger. I planned on publishing my PHP Style Changer for September first-ish on my Experiments Site, but it’s all good. Mine’s completely different than yours so we’ll have some variety :-)

  22. August 24, 2006 by Harmen Janssen

    RE: Nate; Ah thanks for enlightening me :) I must have misunderstood.

  23. August 24, 2006 by Andreas

    That’s funny, I was gonna write about the exact same thing in my next article.

    I use also use sessions for storing the current “style”.

  24. August 24, 2006 by Matthijs

    I might be wrong, but isn’t

    @import ‘/css/.css’;

    this a security problem because $layout comes unfiltered/escaped from a cookie from:

    $layout = (isset($COOKIE[‘layout’])) ? $COOKIE[‘layout’] :

    Or am I wrong?

  25. August 24, 2006 by Emil Stenström

    @Roger: When the php file that generates the HTML is loaded it does not know what $_COOKIE[‘layout’] will contain. Caching what the variable contains means that you won’t be able to switch back.

    The solution (as described in my article) is generating a short snippet of CSS in a file you link to just as if it was a static CSS file. That way the HTML can remain the same and be cached.

  26. August 24, 2006 by Emil Stenström

    @Matthijs: you’re right, if I’m not misstaken that enabled people to insert any HTML to a website.

    Sending “</style><script> code;” pops to mind.

  27. August 24, 2006 by Matthijs

    Another thing is that not every host will allow php shorttags . So it’s saver to use long tags in combination with echo.

  28. August 24, 2006 by nortypig

    I’m surprised nobody has mentioned this, its how i had my blog set up for a long time, with a JavaScript styleswitcher for slickness and a PHP styleswitcher for fallback. It pretty much covers most of everyone - although I hadn’t considered the mixing of cookies and sessions. In that case though I’d consider the client with js disabled AND cookies disabled to be less interested in the experience and more interested in the basic information, but its only my opinion.

    phonophunk.com - may have the url wrong but he’s pretty well know, melbourne guy, has the same setup. I think I got it from ALA originally and just melded both together.

    Like I mentioned before, or not lol, when time permits I’ll reinstate it. But time is fickle at present.

    Consider the option of using both js and php.

  29. August 24, 2006 by nortypig

    I wrote a few articles on the process of melding the two styleswitchers and they have links to the ALA articles

    pigwork.info/?p=446

    Although rereading this one seems a bit like I knew what I was talking about at the time…

    It really wasn’t that hard to do with a bit of jiggle and silly putty Roger.

  30. August 24, 2006 by nortypig

    sorry, as a caveat i should stress that was on a previous design… the current design still hasn’t received print stylesheets :(

    let alone styleswitching (time for bed now)

  31. August 24, 2006 by Anon

    mathijs is the first to point out - shame on the rest of you! - that this is a cross-site scripting security hole.

    You’re taking a user-supplied value and echoing it to your page without being filtered. Bad.

  32. August 24, 2006 by Roger Johansson (Author comment)

    Matthijs, Anon: Yep, you’re right. I got a bit too clever while trimming the script. I’ll fix that and update the article ASAP.

  33. August 24, 2006 by Mike Cherim

    Glad you’re fixing that. Mine used to be subject to cross site scripting (XSS) as well but I addressed that too once someone was nice enough to bring it to my attention. It sure would be nice if there wasn’t a need to prevent this sort of thing. Unfortunately we have to be on the defensive all the times.

  34. August 24, 2006 by Matthijs

    Roger, thanks. Mike is right,it’s very unfortunate we have to spend more time on the security of scripts then the functionality itself.

  35. August 24, 2006 by Glenn

    I have a similar article but more focused on graceful degradation (actually this is my only article).

    To secure from XSS attacks, you can use an in_array() function along with your ternary to check the cookie against a list you created. Security problem solved. This also sets to the default if the cookie does not exist.

    $style = in_array($_COOKIE['style'], $styles) ? $_COOKIE['style'] : 'default';
    

    I think this is a very good approach because we really never deal with a wild list of CSS.

  36. August 24, 2006 by Glenn

    It seemed to have been cut off. Here it is again:

    $style = in_array($_COOKIE['style'], $styles) ? \
    $_COOKIE['style'] : 'default';
    
  37. August 24, 2006 by Roger Johansson (Author comment)

    Ok, I updated the script to prevent the cross-site scripting security hole that was pointed out (thanks!). It’s quite obvious now that I look at it.

    Next on my list is taking a look at your other suggestions.

  38. August 24, 2006 by Roger Johansson (Author comment)

    Hmm. Trying to wrap my head around the HTML caching problem.

    Wouldn’t moving the check to a dynamic CSS file be pointless unless nothing else on the page is dynamically generated?

    Mike: Sorry I beat you to it. I did see you mention it in the article you sent me yesterday. This article was already sitting in my drafts box ready for publishing. Good news that yours is different :-)!

    John [#20]: Yes, that’s what I figure too. Right now I don’t think I’m going to modify the script to use sessions as a fallback. I’ll display an explanatory message when cookies are not accepted instead. I also agree with what dawn is saying in comment #13.

    Sébastien: I’m not using link elements since that would force me to create a “dummy” CSS file for browser filtering with @import.

  39. August 24, 2006 by Pelle

    The optimal solution would be to combine this with a JavaScript solution so that only the ones not having JavaScript has to update their pages.

    You can change the stylesheet in another way without JavaScript also. In Firefox you just choose “View > Page style > a stylesheet”

  40. August 25, 2006 by Mike Cherim

    No problem, Roger. I’m glad actually. I’ve gotten some good food-for-thought from your commenters regarding cookie acceptance and I’ve added a couple of strings to my script that’ll be a clever addition to my changer when I do publish it on the first or so. I’ll post a heads-up in my blog and maybe here too.

    Thanks commenters! :-)

  41. August 25, 2006 by WD Milner

    This should be easily extensible to more than two styles.

    One caveat with adding php_value declarations in the htaccess file is that it won’t work if php is running as CGI rather than as an Apache module. A better solution might be to use ini_set() function in php.

  42. August 27, 2006 by Roger Karlsson

    Interesting stuff. Just out of curiosity, how did the first version of PHP code that was vulnerable to cross-site scripting look like?

  43. August 29, 2006 by MugeSo

    According to RFC2616, Location HTTP respons header is defined as below.

    Location = “Location” “:” absoluteURI

    At the same time, Referer HTTP request header is defined as below.

    Referer = “Referer” “:” ( absoluteURI | relativeURI )

    This means that Referer URI should not be used as Location URI. And because of the same reason, your code $ref = (isset($_SERVER[‘HTTP_REFERER’])) ? $_SERVER[‘HTTP_REFERER’] : “/”; is not valid.

    Should be, $ref = (isset($_SERVER[‘HTTP_REFERER’])) ? $_SERVER[‘HTTP_REFERER’] : “{$_SERVER[‘SERVER_NAME’]}/”;

  44. August 29, 2006 by Roger Johansson (Author comment)

    Pelle: Yes, I’ll try to find time to look at combining this with JavaScript.

    Mozilla and Firefox have let the user select stylesheets for years, but since most other browsers don’t yet have that functionality we have to help them out a little ;-).

    Roger: The first version took the value stored in the cookie and wrote it to the page without any filtering (which now occurs in the second code block, the check for which stylesheet to load).

    MugeSo: Thanks for pointing that out. I’ll update the script later today.

  45. September 1, 2006 by Mike Cherim

    I do deliver on-time, Roger, as promised. Here’s my contribution to those who wish offer users the ability to change their page styles. Here’s the blog post for comments, here’s write-up, and here’s the actual experiment.

    I want to thank those who commented here because they are the ones that inspired me to add the cookie acceptance detection part of my original script. I hate offering users something on a page they can’t use. This will circumvent that possibility.

  46. September 1, 2006 by Roger Johansson (Author comment)

    That looks a lot more detailed, flexible, and professional than my tiny script, Mike. Checking for cookie acceptance is a great idea that I haven’t had time to add yet.

  47. September 1, 2006 by Mike Cherim

    Help yourself to it if you want, Roger. It could come in handy for a number of cookie-dependent scripts I would think.

  48. September 2, 2006 by fry

    There’s a problem in the article that you should take care of. If you check the first line in the code at the beginning

    $layout = (isset($_COOKIE[‘layout’]) && ($_COOKIE[‘layout’] == “zoom”)) ? “main” : “zoom”;

    and the one below in the description

    $layout = (isset($_COOKIE[‘layout’]) && ($_COOKIE[‘layout’] == “zoom”)) ? “zoom” : “main”;

    you’ll see that they’re different. Since you’re switching styles I guess the first one is correct (if set & zoom switch to main else set zoom since main is default) which would make the description wrong.

  49. September 2, 2006 by Roger Johansson (Author comment)

    Mike: Thanks, I might just do that :-).

    fry: They are actually supposed to be different. The first line defines which value to store in the cookie, while the second line defines which stylesheet to load. So unless I am misunderstanding you there is no problem with that.

  50. September 2, 2006 by Mike Cherim

    I broke out the cookie negotiation part of my script in a post comment I made and removed a lot of the commenting to make it clearer to see and thus more usable.

  51. November 11, 2006 by Johan

    why not change just a class in the body?

    This way you can add the classes in one stylesheet, and this way you can have eg four classes for lay-out, font-sizing, whatever.

  52. November 11, 2006 by Roger Johansson (Author comment)

    Johan: That approach would also work. Depending on how different the alternate stylesheet is, that could lead to a much larger file though. And there are other aspects, like having a file structure for different themes etc. Loading a different file is the approach I’d choose, but in some cases what you suggest may be a good choice.

  53. January 20, 2007 by Matthew Pawlewicz

    Hey! THANK YOU SO MUCH FOR THE CODE it works beautifully. theres just one thing, i tried to change it up a bit, so that it displays which css sheet you are using (for instance i am using a text-size changer), but in internet explorer, it displays the OPPOSITE. if you need to see what i have rigged up i would be very happy to give it so that you can help me!!!!

  54. March 6, 2007 by Kimberly

    I am wondering if anyone can point me to a solution for switching two different sets of stylesheets. I mean that I want the users to be able to change not only the style, but also the font size. So they should be able to view each style at small, medium and large font size and switch between layout styles without the font size being changed.

    I guess if I sat around scribbling on paper for long enough, I might be able to come up with a solution in php, but if it’s been done already, it would be nice to see.

    Thanks

  55. April 4, 2007 by Jehzeel Laurente

    Nice one. But is it possible in PHP to auto switch? I don’t know how to do it. T__T I need something like it. If the resolution is 800x600, then the script will get a certain CSS to fit the screens resolution :(

    I hope somebody will post a comment and a link about this here :)

  56. April 5, 2007 by Roger Johansson (Author comment)

    Jehzeel: No, there is no way for PHP to know the screen resolution (or viewport width, since browser window size is more relevant than screen resolution). You need JavaScript for that.

Comments are disabled for this post (read why), but if you have spotted an error or have additional info that you think should be in this post, feel free to contact me.