Transparent Tab & Button Graphics

Tabbed navigation, while not always the perfect choice in all situations, certainly has a lot of advantages. You can display a lot of information in a relatively small space and with near universal recognition, there's really no learning curve for your visitors.

"Is it really that hard to create good looking tabs using graphics software like Photoshop or Fireworks?"

No, but it is time consuming and you have to know what text will be displayed on each tab ahead of time. It'd be slick if you could design a tab once and then generate as many as you needed with whatever text labels you wanted without having to go back and create each one by hand.

Why not just use CSS and avoid images altogether you ask? Well, that's a pretty good solution if your tab design is simple (e.g. no rounded corners or complicated bevels) and you take the time to make sure it displays properly in every browser/version under the sun. I've done this, it's worked... sometimes. Usually, at least for me, it ends up being a bigger pain that just creating the images in the first place. Don't get me wrong, when you've got users who are on dial-up, CSS could very well be the best choice just to keep the page generation time to a minimum. Even though tab images themselves may be quite small (usually < 2KB with the solution I present here), page load is often a result of how many elements are downloaded, not just their overall size. If you litter your page with a lot of tiny images, your users will be facing the hourglass more than you (and certainly they) would like. Not to mention issues surrounding accessibility and search engine optimization. If I've scared you enough where you think a CSS solution might be the better choice for your particular project, I'd like to suggest the following resources:

Having said all that, ahem, when used sparingly and conscientiously tabs can be really effective. And to make good looking tabs easy, I created the following. Let's move on to a demonstration of tab generation...

Let's see it in action

Here is a working example that demonstrates the basic capabilities of this method:

An Example: Choose Background:


The tab images above were generated on-the-fly using some source images and PHP. Scroll the container window and you can clearly see that transparency is handled very well. Create your own tabs by changing the text below and submitting.




1st Tab:

2nd Tab:
3rd Tab:

Some more examples...

This method is not limited to tabs.... you can also create buttons on the fly, even those pretty glass-like reflective ones above.

Mouseovers are possible as well by creating another version of the tab image for the hover state and referencing it by scope...

Active tabs in this example are taller than inactive tabs, allowing room for the highlight...

Any horizontally reflective design is especially easy to create, including concave corners like these...

The tab's text can be boxed and the more complicated visual effects removed for a simple look....

With full transparency support, background images can still be visible behind inactive tabs like these...






Well, you get the idea... let's move on to the PHP to see how this is accomplished.


Using PHP & GD to generate images

From the PHP manual:

PHP is not limited to creating just HTML output. It can also be used to create and manipulate image files in a variety of different image formats, including gif, png, jpg, wbmp, and xpm. Even more convenient, PHP can output image streams directly to a browser. You will need to compile PHP with the GD library of image functions for this to work. GD and PHP may also require other libraries, depending on which image formats you want to work with.
To read more about the GD library and compiling it into PHP, check out these resources:
PLEASE NOTE! The imagecolorallocatealpha function used in this script requires GD 2.0.1 or later (2.0.28 or later is recommended). Failure to use more current versions of these libraries can cause numerous errors and server load.

In the demo, we're dynamically setting the text of the tab and what I refer to as scope, which is handy-dandy way of referencing certain tab styles. The styles shown are just examples and I'm happy to provide them to get you started... but you'll likely want to create your own tab designs.

But let's get to the nitty-gritty, which is the code to generate the tabs in the first place:

 * Email: Brian Barrett <brian [at] cristina [dot] org>
 * This code is included into tabs.php and outputs a 24bit PNG tab image with
 * transparency support.  By passing in a scope (e.g. sky_blue, yellow_gradient)
 * the script will use different source images to compile the tab.
 * Source files expected:
 *      left_first_active.png           left-most of the first active tab
 *      left_active.png                 left-most (usually rounded corner) of the tab
 *      middle_active.png               single-pixel wide image to tile in the middle
 *      right_active.png                left-most (usually rounded corner) of the tab
 *      left_first_inactive.png         left-most of the first tab (no overlay shadow)
 *      left_inactive.png               left-most (usually rounded corner) of the tab
 *      middle_inatictive.png           single-pixel wide image to tile in the middle
 *      right_inactive.png              left-most (usually rounded corner) of the tab
 *      tab_shadow.png                  included in image_base but up to you to put on page
 *      tab_bg.png                      included in image_base but up to you to put on page
 * Also included is a cache_image() function that will tell the browser to
 * cache the returned image for 3 hours (default) for improved performance.
 *      text    text to print on the tab (urlencode funky chars)
 *      scope   type of tab to display, current options include:
 *                      "yellow_gradient" (active orange-yellow gradation, inactive white)
 *                      "default" (white live, grey stale)
 *      is_live         make tab active - true/false
 *      is_first        is first tab in row - true/false
 *      get_tab_image($text,$scope,$is_live,$is_first)
 *      load_config($file_path)
 *      cache_image($seconds)
 *      rgb2hex($hex_or_rbg)
if (!function_exists('get_tab_image')) {
function get_tab_image($text = "TAB",$scope = "default",$is_live = false,$is_first = false) {
        $image_base = "/images/tabs/";
        // Get config file..
        $config = load_config("{$image_base}{$scope}/config.txt");
        // Register box
        $box = imagettfbbox ($config[fontsize], 0, $config[font], $text);
        // Find out the width and height of the text box
        $textW = $box[2] - $box[0];
        $textH= $box[3] - $box[5];
        // Set image dimensions
        $width = $textW + ($config[paddingx]*2);
        // Setup some moderator variables...
        if (!$is_live) {
                $mod = "_inactive";
        } else {
                $mod = "_active";
        if ($is_first) {
                $mod2 = "_first";
        // Create the left side of the tab and get size...
        $img_left = imagecreatefrompng($config[image_base] . "left{$mod2}{$mod}.png");
        $left_x = imagesx($img_left);
        $left_y = imagesy($img_left);
        // Create the right side of the tab and get size...
        $img_right = imagecreatefrompng($config[image_base] . "right{$mod}.png");
        $right_x = imagesx($img_right);
        $right_y = imagesy($img_right);
        // Create the middle of the tab and get size...
        $img_middle = imagecreatefrompng($config[image_base] . "middle{$mod}.png");
        $middle_x = imagesx($img_middle);
        $middle_y = imagesy($img_middle);
        // Use the middle (tiled) image to get overall height...
        $height = imagesy($img_middle);
        // Bottom left corner of text...
        $textx = ($config[paddingx]*2)/2;
        $texty = $height - $config[paddingy]/2;
        // Create the image...
        $img = imagecreatetruecolor($width,$height);
        // Define active and inactive text/shadow colors...
        if ((strlen($config[active_color]) < 10) && !strstr($config[active_color], ",")) {
                $config_active_color = rgb2hex($config[active_color]);
        } else {
                $config_active_color = explode(",",$config[active_color]);
        if ((strlen($config[inactive_color]) < 10) && !strstr($config[inactive_color], ",")) {
                $config_inactive_color = rgb2hex($config[inactive_color]);
        } else {
                $config_inactive_color = explode(",",$config[inactive_color]);
        if ((strlen($config[shadow_active_color]) < 10) && !strstr($config[shadow_active_color], ",")) {
                $config_shadow_active_color = rgb2hex($config[shadow_active_color]);
        } else {
                $config_shadow_active_color = explode(",",$config[shadow_active_color]);
        if ((strlen($config[shadow_inactive_color]) < 10) && !strstr($config[shadow_inactive_color], ",")) {
                $config_shadow_inactive_color = rgb2hex($config[shadow_inactive_color]);
        } else {
                $config_shadow_inactive_color = explode(",",$config[shadow_inactive_color]);
        $active_color = imagecolorallocate($img,$config_active_color[0],$config_active_color[1],$config_active_color[2]);
        $inactive_color = imagecolorallocate($img,$config_inactive_color[0],$config_inactive_color[1],$config_inactive_color[2]);
        $shadow_active_color = imagecolorallocate($img,$config_shadow_active_color[0],$config_shadow_active_color[1],$config_shadow_active_color[2]);
        $shadow_inactive_color = imagecolorallocate($img,$config_shadow_inactive_color[0],$config_shadow_inactive_color[1],$config_shadow_inactive_color[2]);
        // Setup image transparency...
        imagealphablending($img, false);
        $colorTransparent = imagecolorallocatealpha($img, 0, 0, 0, 127);
        imagefill($img, 0, 0, $colorTransparent);
        imagesavealpha($img, true);
        // Fill image with background color...
        // Fill center of tab with the middle image...
        $i = $left_x;
        while ($i < ($width - $right_x)) {
                imagecopy($img, $img_middle, $i, 0, 0, 0, $middle_x, $middle_y);
                $i = $i + $middle_x;
        // Append left cap to the tab...
        imagecopy($img, $img_left, 0, 0, 0, 0, $left_x, $left_y);
        // Append right cap to the tab...
        imagecopy($img, $img_right, $width - $right_x, 0, 0, 0, $right_x, $right_y);
        // Reset alpha blanding mode so text overlays properly...
        imagealphablending($img, true);
        // Write text...
        if ($is_live) {
                // Write drop-shadowed text first...
                if ($config[shadow_active_offsetx]) {
                        imagettftext($img, $config[fontsize], 0, $textx+$config[shadow_active_offsetx], $texty+$config[shadow_active_offsety], $shadow_active_color, $config[font], $text);
                imagettftext($img, $config[fontsize], 0, $textx, $texty, $active_color, $config[font], $text);
        } else {
                // Write drop-shadowed text first...
                if ($config[shadow_inactive_offsetx]) {
                        imagettftext($img, $config[fontsize], 0, $textx+$config[shadow_inactive_offsetx], $texty+$config[shadow_inactive_offsety], $shadow_inactive_color, $config[font], $text);
                imagettftext($img, $config[fontsize], 0, $textx, $texty, $inactive_color, $config[font], $text);
        // Set header info (including cache-control)...
        //header("Content-type: image/png");
        // Send the image and cleanup...
if (!function_exists('load_config')) {
function load_config($file_path) {
        $stored = file($file_path);
        foreach ($stored as $line) {
                if ( (substr($line,0,1) != ";") && (substr($line,0,1) != ";") && (substr($line,0,1) != "#") ) {
                        $pair = explode(":",$line);
                        $config[trim($pair[0])] = str_replace(";","",str_replace("\"","",str_replace("'","",trim($pair[1]))));
        return $config;
if (!function_exists('cach_image')) {
function cache_image($seconds = 10800) { // default = 3 hrs
        header('Expires: ' . gmdate('D, d M Y H:i:s',time()+$seconds) . ' GMT');
        header("Cache-Control: max-age=$seconds");
        header("Content-type: image/png");
if (!function_exists('rgb2hex')) {
function rgb2hex($c){
        if(!$c) return false;
        $c = trim($c);
        $out = false;
        if(preg_match("/^[0-9ABCDEFabcdef\#]+$/i", $c)){
                $c = str_replace('#','', $c);
                $l = strlen($c);
                if($l == 3){
                        $out[0] = $out['r'] = $out['red'] = hexdec(substr($c, 0,1));
                        $out[1] = $out['g'] = $out['green'] = hexdec(substr($c, 1,1));
                        $out[2] = $out['b'] = $out['blue'] = hexdec(substr($c, 2,1));
                }elseif($l == 6){
                        $out[0] = $out['r'] = $out['red'] = hexdec(substr($c, 0,2));
                        $out[1] = $out['g'] = $out['green'] = hexdec(substr($c, 2,2));
                        $out[2] = $out['b'] = $out['blue'] = hexdec(substr($c, 4,2));
                }else $out = false;
        }elseif (preg_match("/^[0-9]+(,| |.)+[0-9]+(,| |.)+[0-9]+$/i", $c)){
                if(strstr($c, ","))
                        $e = explode(",",$c);
                else if(strstr($c, " "))
                        $e = explode(" ",$c);
                else if(strstr($c, "."))
                        $e = explode(".",$c);
                else return false;
                if(count($e) != 3) return false;
                $out = '#';
                for($i = 0; $i<3; $i++)
                        $e[$i] = dechex(($e[$i] <= 0)?0:(($e[$i] >= 255)?255:$e[$i]));
                for($i = 0; $i<3; $i++)
                        $out .= ((strlen($e[$i]) < 2)?'0':'').$e[$i];
                $out = strtoupper($out);
        }else $out = false;
        return $out;

The above code can be put into an include file (usually in a sub-directory like /includes/ and then we create a simple tabs.php script that takes in passed variables and calls the get_tab_image() function:

include ("includes/");
if (!$_GET[scope]) { $_GET[scope] = "yellow_gradient"; }

This also gives you an easy way of specifying a default scope (tab style) like I've done above. You could just as easily put everything into one tabs.php script... it might even be faster on execution time, although I haven't done any formal testing of that.

Each tab or button is configured using a config.txt file stored in the directory that contains the component images for that button/tab (e.g. /images/tabs/yellow_gradient/config.txt).

Here's an example of one of the config files used on this site:

image_base: "/images/tabs/sky_blue/";
font: "/includes/verdanab.ttf";
fontsize: 10;
paddingx: 14;
paddingy: 11;
active_color: #FFFFCC;
inactive_color: #E6E6E6;
shadow_active_color: #000000;
shadow_active_offsetx: 1;
shadow_active_offsety: 1;
shadow_inactive_color: #000000;
shadow_inactive_offsetx: 1;
shadow_inactive_offsety: 1;

Originally, I had all the different configuration parameters hard-coded within the but that quickly became cumbersome with so many examples. Loading the tab or button config options from a text file isn't the best method if you're looking for the absolute fastest generation times but it was a necessary evil here. If your site will only contain a few button or tab styles, I'd recommend hard coding the config options in the script.

Next, let's get started creating the tab source images...

Creating the tab source graphics

To create the source images you see in the demo, I used Fireworks with a template file that looks like this:

And the same file but with slicing areas turned on:

In total, there are 10 component images to each tab style:

Image File Name Description
left_first_inactive.png The left edge of the inactive first tab
left_inactive.png The left edge of an inactive tab
middle_inactive.png The single-pixel image to tile through an inactive tab's center
right_inactive.png The right edge of an inactive tab
left_first_active.png The left edge of the active first tab
left_active.png The left edge of an active tab
middle_active.png The single-pixel image to tile through an active tab's center
right_active.png The right edge of an active tab
tab_bg.png The background image to run behind the tabs (extends the content seperator line)
tab_shadow.png The shadow graphic placed to the right of the last tab

The buttons are very simple to create with really only three components necessary:

I used Fireworks to create the PNG source images because frankly, that's exactly what Fireworks was made for. I can't sing the praises of Fireworks enough and if you haven't sunk your teeth into it yet, I HIGHLY recommend it.

That's not to say however that you can't develop these source graphics in other software. I didn't take the time to create all the source templates in Photoshop as native PSD documents but I did do the Default tab example... just as a helpful guide to those determined to use Photoshop. You can download it (and everything else you might want) from the next page...

Download Source & Images

You can download the src for the PHP scripts, tab template starters and component graphics here.

Full Package
Download all the PHP, component images and template source images to begin generating the example tabs/buttons you've already seen.
 [] (658.49 KB)

PHP Tab Generation Code
Download only the PHP include file necessary to generate the transparent tab and button graphics.
 [] (2.66 KB)

PHP Image SRC File
Download only the tabs.php file that is referenced in the IMG src attribute on your HTML pages.
 [] (275.00 B)

PNG Tab & Button Template Images
Download template images for the tab/button examples you've seen on this site. By loading any one of these into Fireworks you can quickly export the tab component images necessary for building your own.
 [] (490.44 KB)

PSD (Photoshop) Default Tab Template Image
I highly recommend using (now Adobe, formerly Macromedia) Fireworks to create your tab templates but if you are unfamiliar with Fireworks and have Photoshop already, download this PSD source. It has all the slices in place and should be easy to change the style/colors to suit your needs.
 [Default.psd] (84.46 KB)