This quick tutorial will demonstrate how you can set up your Drupal site to allow registered users to create custom themes which they can apply to other content items (nodes).
Feature requirements
- Allow users to customize the appearance (theme) of an individual node which they created.
- Allow users to use a single customized theme for multiple nodes.
- Restrict users to customizing only a limited set of page elements (e.g., page background, block color, etc.).
- Enable users to preview their customized themes before actually applying it to a node.
Technical requirements (Drupal 6)
- Content Construction Kit (CCK)
- Node Reference Module
- Color Picker Module
- Color Picker Widget
- Image Field Module
- Views 2 Module
- Content Templates Module
Development Steps:
Before getting started, please note that this is not a tutorial on how to set up a Drupal site or install and enable modules. Rather, this tutorial is written under the assumption that readers are already familar with these steps. So, please proceed, if not already, to install the required modules listed above under Technical Requirements.
Step 1: Set Up Content Types
Create a basic content type for Custom Themes. I personally left the "Body field label" blank to disable the use of the body field as I had no use for it. However, if you have a need for it, feel free to enable it. You could use it to allow users to add dummy text to preview with the customized theme.
At this point we are not going to add any additional fields, as we will revisit this content type and add them after identifying which page elements we want to allow registered users to customize.
For the sake of this tutorial, let's assume that we want to allow users to customize nodes for Events. So, create a content type called Events with the associated fields (e.g., date, location, etc.) and add an additional field using the node reference widget called "Custom Theme."
Here is a quick outline of how I configured the node reference field, Custom Theme:
- Widget type: select list
- Required: unchecked
- Content types that can be referenced: Custom Themes
- Advanced: Nodes that can be referenced (view)
(Note: this step is extra and not required, but I chose to use the views module to create a default view to display a user's Custom Themes in a more detailed manner. For instance, my Custom Theme was 03/01/2010.)
Step 2: Set Up Node Detail Page (Event)
Focusing on the node (event) detail page in which we are allowing users to apply a customized theme, we want to ideally create a test node (event), layout all content associated with our node, and add some test data. It will be much easier to have a visual guide as we identify regions and their html elements in which we want to allow users to customize.
The following is an example of how my event page is laid out. The areas in which users are able to customize are:
- the background (light blue area)
- content area (background, text color, link color)
- right side bar blocks (background, text color, link color)
Step 3: Add fields to Custom Themes content type
After you have identified the regions and html elements that you want to customize, you should add respective cck fields to your Custom Themes content type for users to easily interact with and control desired theme changes.
Here is a snapshot of the fields I am currently using for the Custom Themes content type.
Step 4: Create Custom Theme
Now that you have all of your Custom Theme cck fields in place, go and create a new custom theme and fill out the form according to your theme/styling requirements. I will be the first to admit that I am not an adept designer, so please forgive me for any unsightly design elements.
Here is what my Custom Theme form looks like after I have added all of my cck fields and added custom colors and background images.
Is this starting to feel like MySpace yet?
After you've completed the form, please save your changes and revisit the test node (node) that you created in Step 2, and edit the node (event) to apply the newly created Custom Theme.

Step 5: Apply Custom Theme to Content Type Body Displays
Now that we have our content types in place along with some test data we can now update the detail pages for both content types to reflect the Custom Theme. Why apply the Custom Theme to the detail page of the Custom Theme? Well, as specified within the requirements outlined above, I want users to view the Custom Theme before actually applying it to a node (event).
Using the content template module, I created two new body templates:
- node-event-body.tpl.php
- node-custom_theme-body.tpl.php)
Then, I placed them within the contemplates/ directory created within the all/ directory of my site. After creating these files, make sure that the Body columns for these content types are marked as Enabled: Disk on the Content types > Content Templates Tabbed Page (/admin/content/types/templates). This signifies that these new body templates are actually recognized and will be used for Body displays.
The goal now is to take the information associated with our custom theme node and build out some css within the body templates to alter the styling of our nodes.
Here is the code that I used for the Custom theme content type and added to the node-custom_theme-body.tpl.php file.
<style type="text/css">
.page-node fieldset {
background: none;
}
.page-node #page {
background: <?php print $node->field_page_bkgimage[0]['filepath'] ? 'url(/' . $node->field_page_bkgimage[0]['filepath'] . ')' : 'none';?>
<?php print $node->field_page_bkgcolor[0]['value'] ? $node->field_page_bkgcolor[0]['value'] : null;?>
<?php print $node->field_page_bkgimage_repeat[0]['value'] ? $node->field_page_bkgimage_repeat[0]['value'] : null;?>
<?php print $node->field_page_bkgimage_x[0]['value'] ? $node->field_page_bkgimage_x[0]['value'] : null;?>
<?php print $node->field_page_bkgimage_y[0]['value'] ? $node->field_page_bkgimage_y[0]['value'] : null;?>
;
}
.page-node #content .post-box {
background: none;
border: none;
}
.page-node #content,
.page-node #content #content-inner {
background: <?php print $node->field_body_bkgimage[0]['filepath'] ? 'url(/' . $node->field_body_bkgimage[0]['filepath'] . ')' : 'none';?>
<?php print $node->field_body_bkgcolor[0]['value'] ? $node->field_body_bkgcolor[0]['value'] : null;?>
<?php print $node->field_body_bkgimage_repeat[0]['value'] ? $node->field_body_bkgimage_repeat[0]['value'] : null;?>
<?php print $node->field_body_bkgimage_x[0]['value'] ? $node->field_body_bkgimage_x[0]['value'] : null;?>
<?php print $node->field_body_bkgimage_y[0]['value'] ? $node->field_body_bkgimage_y[0]['value'] : null;?>
;
<?php print $node->field_body_txtcolor[0]['value'] ? 'color:' . $node->field_body_txtcolor[0]['value'] . ';' : null;?>
}
.page-node #content a,
.page-node #content a:active,
.page-node #content a:visited,
.page-node #content a:hover {
<?php print $node->field_body_lnkcolor[0]['value'] ? 'color:' . $node->field_body_lnkcolor[0]['value'] . ';' : null;?>
}
.page-node .sidebar .block,
.page-node .sidebar .block h2,
.page-node .sidebar .block .block-inner {
background: none;
}
.page-node .sidebar .block,
.page-node .sidebar .block h2 {
<?php print $node->field_block_txtcolor[0]['value'] ? 'color:' . $node->field_block_txtcolor[0]['value'] . ';' : null;?>
}
.page-node .sidebar .block a,
.page-node .sidebar .block a:active,
.page-node .sidebar .block a:visited,
.page-node .sidebar .block a:hover {
<?php print $node->field_block_lnkcolor[0]['value'] ? 'color:' . $node->field_block_lnkcolor[0]['value'] . ';' : null;?>
}
.page-node .sidebar .block .block-inner {
padding-bottom: 10px;
background: <?php print $node->field_block_bkgimage[0]['filepath'] ? 'url(/' . $node->field_block_bkgimage[0]['filepath'] . ')' : 'none';?>
<?php print $node->field_block_bkgcolor[0]['value'] ? $node->field_block_bkgcolor[0]['value'] : null;?>;
}
</style>
<?php print $node->body ?>
Here is the code that I used for the Event content type and added to the node-event-body.tpl.php file.
<?php
$theme_node = node_load($node->field_theme[0]['nid']);
if ($node->field_theme[0]['nid']):
?>
<style type="text/css">
.page-node.node-type-event #page {
background: <?php print $theme_node->field_page_bkgimage[0]['filepath'] ? 'url(/' . $theme_node->field_page_bkgimage[0]['filepath'] . ')' : 'none';?>
<?php print $theme_node->field_page_bkgcolor[0]['value'] ? $theme_node->field_page_bkgcolor[0]['value'] : null;?>
<?php print $theme_node->field_page_bkgimage_repeat[0]['value'] ? $theme_node->field_page_bkgimage_repeat[0]['value'] : null;?>
<?php print $theme_node->field_page_bkgimage_x[0]['value'] ? $theme_node->field_page_bkgimage_x[0]['value'] : null;?>
<?php print $theme_node->field_page_bkgimage_y[0]['value'] ? $theme_node->field_page_bkgimage_y[0]['value'] : null;?>
;
}
.page-node.node-type-event #content .post-box {
background: none;
border: none;
}
.page-node.node-type-event #content,
.page-node.node-type-event #content #content-inner {
background: <?php print $theme_node->field_body_bkgimage[0]['filepath'] ? 'url(/' . $theme_node->field_body_bkgimage[0]['filepath'] . ')' : 'none';?>
<?php print $theme_node->field_body_bkgcolor[0]['value'] ? $theme_node->field_body_bkgcolor[0]['value'] : null;?>
<?php print $theme_node->field_body_bkgimage_repeat[0]['value'] ? $theme_node->field_body_bkgimage_repeat[0]['value'] : null;?>
<?php print $theme_node->field_body_bkgimage_x[0]['value'] ? $theme_node->field_body_bkgimage_x[0]['value'] : null;?>
<?php print $theme_node->field_body_bkgimage_y[0]['value'] ? $theme_node->field_body_bkgimage_y[0]['value'] : null;?>
;
<?php print $theme_node->field_body_txtcolor[0]['value'] ? 'color:' . $theme_node->field_body_txtcolor[0]['value'] . ';' : null;?>
}
.page-node.node-type-event #content a,
.page-node.node-type-event #content a:active,
.page-node.node-type-event #content a:visited,
.page-node.node-type-event #content a:hover {
<?php print $theme_node->field_body_lnkcolor[0]['value'] ? 'color:' . $theme_node->field_body_lnkcolor[0]['value'] . ';' : null;?>
}
.page-node.node-type-event .sidebar .block,
.page-node.node-type-event .sidebar .block h2,
.page-node.node-type-event .sidebar .block .block-inner {
background: none;
}
.page-node.node-type-event .sidebar .block,
.page-node.node-type-event .sidebar .block h2 {
<?php print $theme_node->field_block_txtcolor[0]['value'] ? 'color:' . $theme_node->field_block_txtcolor[0]['value'] . ';' : null;?>
}
.page-node.node-type-event .sidebar .block a,
.page-node.node-type-event .sidebar .block a:active,
.page-node.node-type-event .sidebar .block a:visited,
.page-node.node-type-event .sidebar .block a:hover {
<?php print $theme_node->field_block_lnkcolor[0]['value'] ? 'color:' . $theme_node->field_block_lnkcolor[0]['value'] . ';' : null;?>
}
.page-node.node-type-event .sidebar .block .block-inner {
padding-bottom: 10px;
background: <?php print $theme_node->field_block_bkgimage[0]['filepath'] ? 'url(/' . $theme_node->field_block_bkgimage[0]['filepath'] . ')' : 'none';?>
<?php print $theme_node->field_block_bkgcolor[0]['value'] ? $theme_node->field_block_bkgcolor[0]['value'] : null;?>;
}
</style>
<?php endif; ?>
.....
As you will notice, there is only a small difference between the two templates. For the events, I needed to load the node reference for the selected custom theme. The way in which we achieved this is to use the node_load ($param = array(), $revision = NULL, $reset = NULL) function (api reference) passing it the nid of the selected custom theme. If the custom theme node($theme_node) is found we will then build out the css using the data provided by the custom theme($theme_node) node.
Note: if a node is not found using the node_load() function, FALSE will then be returned.
Step 6: View Your Customized Theme
Now that you have your css being constructed according to the custom theme node, you should take a look at both nodes. For the Custom Theme node this will, again, serve as a preview page for users before they actually apply it to another node(event).
Here is my Custom Theme node after saving it and viewing it in its body display.
And here is the node(event) in its body display using the Custom Theme.
Developer Notes
- Be aware of caching your detail pages for content types using custom themes as there will be a delay in updates to the Custom Theme.
- Color picker widget module: Currently, when you leave a color picker field blank or with the default '#', you will get a form submission error stating that the value is not a valid hex value. Unfortunately this isn't practical for a custom theme, as a user may not want a color for a given html element. So, although this is taboo, if you open up the colorpicker_cck.module file, you can replace line 55 to avoid this. In other words:
Replace:
if ($item['value'] !== '#' && !preg_match('/^#(?:(?:[a-f\d]{3}){1,2})$/i', $item['value'])){
with
if ($item['value'] !== '' && $item['value'] !== '#' && !preg_match('/^#(?:(?:[a-f\d]{3}){1,2})$/i', $item['value'])) {
you should now be good to go.
Forum One News
Dan's first job as a web developer was at a charter school in Camden, New Jersey. In addition to his required tasks to teach computers and act as network technician, he took the initiative to...









Comments
Panels
Another way of simply doing template customizations is by using the Panels module or by using the tpls or a custom module that handles the template hooks directly.
Cheers for the great article.
Ari
Meetai.com
Many thanks
Thanks a lot for a clear step by step tutorial. As for me, I've recently started using it and I'm not good at it yet. I use Drupal 7 but I've found only books in Drupal 6 at pdf search http://www.pdfok.com . That is why such tutorials are of great help to me. Keep on going!
Great Buddy how you know
Great Buddy how you know about this in very details, it is really very interesting and knowledgeable things,
Dan, Thanks for the feedback.
Dan,
Thanks for the feedback. I really don't see any issues with Drupal 7. Although I haven't looked into it yet, any necessary changes that would be required would be a fairly simple process. When I get a chance to work with Drupal 7 I will definitely post information about the necessary steps.
Very much what I've been looking for
Hi Dan and thanks for this,
Sorry I'm in a bit deep here. I'm used to custom theming adding tpl.php files and declaring css from the .info file of a theme. I've not used contemplates before.
What I'm trying to do is change the color of all links (navigation menu + body + sidebar) so that the user can upload a background image and choose the link color to reflect the color tone of the image. This would change on a per node basis.
So a user creates content, adds a bkgimage, color picks the a:active / hover and the node is rendered with override to css links in all regions.
I've tried your method above
I have created the custom_theme content type and another content type to apply the theme to (themed_page) with node reference to custom_theme.
I've got a test "themed_page" node referenced to a CT1 custom_theme content item
When I try to reproduce your example I get the css in proper syntax with injected within the node div instead of the header. Does that work?
And having trouble with the rendering of the node content as well, but that's probably due to css classes not matching and not using the variables right.
Can you give an exemple of the full node output (your tpl.php files) with the css and the rest of the output / use of variables.
Can the primary nav be color picked without being inside the main node content?
Is there a way to grab a color picker CCK field value and get that to override the css before the page is rendered? maybe using drupal_add_css (dynamic css generated by contemplate)
Thanks again Dan, hope you have some time to chat more about this.
Hey Gael, I apologize for the
Hey Gael,
I apologize for the late response. If you are still having trouble in getting the setup to work I would be more than happy to help you out. Feel free to email me at dcrowder@forumone.com so that we could get you up and rolling.
-Dan
More than welcome!
Hey, you are more than welcome. Definitely enjoy it.
I would also love to see what you do with it.
Very good tutorial!
Hi Dan,
thanks for this great tutorial!
Cheers,
Daniel
Post new comment