Now, why would you want this? Hacking on files outside your theme directory is bad practice. It complicates maintenance and you end up with bugs that are hard to track. Another reason is that adding tons of styles into your "style.css" file can eventually make it unwieldy and error prone.

There are two approaches to overriding styles sheets outside the theme directory. One is to automatically load a modified version with drupal_add_css() and setting a 'theme' parameter so the cascading order overrides them. ("Theme" styles always print out after "module" styles.) The other is to manipulate the PHPTemplate variable $css directly so it depends entirely on your modified version.

The following code should be inside your template.php file and rename MYTHEME to the name of your theme.

First approach:

The function:

/**
 * Used to detect styles outside the theme folder and attach a modified version.
 */
function MYTHEME_attach_module_styles($css) {
  foreach ($css as $media => $types) {
    foreach ($types['module'] as $file => $preprocess) {
      // modified version detected here. basename() returns the file name.
      if (file_exists($attach = path_to_theme() .'/styles/mod-'. basename($file))) {
        // Use theme for easy overriding through CSS cascade.
        drupal_add_css($attach, 'theme', $media, $preprocess);
      }
    }
  } 
}

Where it's called:

/**
 * Overridden only to add stylesheets before drupal_get_css gets called
 * from phptemplate_page. This minimizes preprocessed style sheets.
 */
function MYTHEME_page($content, $show_blocks = TRUE) {
  // Conditionally attach modified module styles.
  MYTHEME_attach_module_styles(drupal_add_css());
  return phptemplate_page($content, $show_blocks);
}

What's going on here? MYTHEME_page() overrides phptemplate_page(). This isn't necessary but phptemplate_page() calls drupal_get_css() which renders all the preprocessed style sheets. Adding to the styles array before it will make it more efficient. Afterwards, phptemplate_page() is called directly so it can go through and render the page.

The styles array is returned from drupal_add_css(). That's fed into MYTHEME_attach_module_styles() which scans through all the style sheets for the page. When a style sheet prepended with "mod-" within the "styles" folder matches the module style it will pick it up overriding all the rulesets is inside mod-MODULE.css. Prepending it differentiates from the original style, nice for debugging. Remember to use the exact same selector from the original style or something more specific. If the css selector inside the original style is more specific, CSS cascading won't take effect. This makes it very easy to manage separate module styles specific to your theme.

Second approach:

The function:

/**
* Replace 'module' styles when an override exists in "styles/" directory in
* the theme folder. It can also be disabled by appending ".disabled" to the
* style name. Style order is maintained.
*/
function MYTHEME_override_css($css) {
  $css_override = array();  
  
  foreach ($css as $media => $types) {
    // Focus on module styles.
    foreach ($types['module'] as $file => $preprocess) {
      // override detected here. basename() returns the file name.
      if (file_exists($override = path_to_theme() .'/styles/'. basename($file))) {
        $css_override[$media]['module'][$override] = $preprocess;
      }
      // Appending ".disabled" to the style disables it. Useful if
      // you want to manage the styles from the theme style.
      elseif (!file_exists($override .'.disabled')) {
        $css_override[$media]['module'][$file] = $preprocess;
      }
    }
    // Append theme styles.
    if (!empty($types['theme'])) {
      $css_override[$media]['theme'] = $types['theme'];
    }
  }
  if (!empty($css_override)) {
    $css = $css_override;
  }
  
  return $css;
}

Where it's called:

function _phptemplate_variables($hook, $vars) {
  if ($hook == 'page') {
    $vars['css'] = MYTHEME_override_css($vars['css']);
    $vars['styles'] = drupal_get_css($vars['css']);
  }
  
  return $vars;
}

With this approach, if the module style is detected within the "styles" folder. It will completely omit the original style sheet and include the themes version. This does mean more maintenance and more preprocessed styles. You can also completely disable styles sheets and that can get complicated trying to manage them also but it's presented here to show you the possibilities. The first approach is recommended.

After the function is setup, all you need to do is copy the modified styles into your "styles" directory. It will be pretty much automated from there.

Two things to watch out for in both approaches:

  1. Images included with the style sheet will break. You must re-link them or you'll get a bunch of 'file not found' errors in you logs.
  2. Style sheets outside the theme sharing the same name may interfere with each other. This usually isn't a problem but it's something to watch out for.

Comments

BarisW’s picture

I have a Content Type with the option to add a CSS file. With this CSS file editors have the option to override styling for a specific node.
To add this CSS file to the HEAD of the page I use this piece of code in my template.php:

function phptemplate_preprocess_page(&$variables) {
  if  ($node = menu_get_object()) {
	if($node->field_cssfile[0]["filepath"]){ 
		$variables['css']['all']['theme'][$node->field_cssfile[0]["filepath"]] = true; 
		$variables['styles'] = drupal_get_css($variables['css']);
	}
  }
}

Baris Wanschers (@BarisW)
Drupal specialist

Wolfflow’s picture

If not can someone help and add D6 code? Thanks

Contact me for drupal projects in English, German, Italian, Drupal Hosting Support.

pfrenssen’s picture

How to do this in D6 and D7 is explained here: http://drupal.org/node/263967

dman’s picture

I was surprised to find that even in D7, you can't have both per-module css files as overrides in your theme AND the efficient conditional inclusion only when needed that #attached etc supports.
The magic override-matching-filename process does work in drupal_get_css(), but only if you've also registered the per-module css file in your themes .info.

So here's a THEME_css_alter() that behaves as I thought we would want it to.

/**
 * Use module-specific css files if they are available in this theme.
 *
 * If a module is attempting to add a css file
 * (using its .info, drupal_add_css or #attached)
 * and we supply a file with the identical basename in our own /css folder,
 * - then use that as an override instead.
 *
 * This restores expected behaviour which is not correctly supported when you
 * add a css file via theme.info .
 * The problem is that statically including a per-module css file in theme.info
 * as described as https://www.drupal.org/node/263967 will
 * include it on every page, and therefore ruin the conditional inclusion
 * of per-module css we expect.
 *
 * @see https://www.drupal.org/node/155400
 * for a drupal6 description of this sort of work-around.
 *
 * Implements hook_css_alter().
 */
function THEME_css_alter(&$css) {
  // Avoid numerous file_exists() calls by running a quick initial scan of
  // the css files we really do have, as it's going to be a many-to-few ratio.
  $theme_css_files = file_scan_directory(path_to_theme() . '/css', '/\.css$/', array('recurse' => FALSE, 'key' => 'filename'));
  // Result array is indexed by basename, as that's all we look for.

  foreach ($css as $key => &$info) {
    if (isset($theme_css_files[basename($key)])) {
      // Changing the data (filename) of the current record is the quickest
      // way to override it, without disrupting the other ordering rules.
      $info['data'] = $theme_css_files[basename($key)]->uri;
    }
  }
}