In keeping with the spirit of moving the furniture around, it was clean-up time. My CSS style sheets were looking a little messy. I did not fancy going through these monsters line-by-line, so I wrote a little Python script to help.
I wanted something simple and efficient, which saves me from labouring through my badly-formatted CSS. The script scans HTML and CSS files locally, detecting CSS rules that are not being used and outputs a report. Since I’m using more than one style sheet, the report is organised by CSS file.
In brief:
- cssutils
is a bit chatty, so the warnings are suppressed. I’m not building or validating my CSS; I’m only looking for unused CSS rules.
- The extract_base_selector
function extracts base CSS class names, cleaning the selectors by removing pseudo-classes and placeholders.
- The find_unused_css
function scans all HTML files, collects used class names and then compares them with those in all CSS files.
- The report shows classes on the CSS files which are not found anywhere in the HTML files.
- You will need to go through your style sheets and comment/remove the obsolete CSS.
Python script to find unused CSS
Python 3.5+ is required since typing.List
and typing.Tuple
are available from 3.5 onwards.
Install the non-standard library module cssutils
with:
pip install cssutils
The script
#!/usr/bin/env python3
import os
import re
import cssutils
import logging
from typing import List, Tuple
from datetime import datetime
# Suppress cssutils warnings
cssutils.log.setLevel(logging.CRITICAL)
cssutils.log.setLevel(logging.FATAL)
def extract_base_selector(selector: str) -> List[str]:
base_selectors = []
for sel in selector.split(','):
clean_sel = re.sub(r':[\w-]+', '', sel)
clean_sel = re.sub(r'\$.*?\$', '', clean_sel)
classes = re.findall(r'\.([\w-]+)', clean_sel)
base_selectors.extend(classes)
return base_selectors
def find_unused_css(html_dir: str, css_files: List[str]) -> List[Tuple[str, str]]:
used_classes = set()
for root, _, files in os.walk(html_dir):
for file in files:
if file.endswith('.html'):
with open(os.path.join(root, file), 'r') as f:
content = f.read()
found_classes = re.findall(r'class=[\'"]([^\'"]+)', content)
for class_list in found_classes:
used_classes.update(class_list.split())
unused_rules = []
for css_file in css_files:
try:
parser = cssutils.CSSParser(raiseExceptions=False, validate=False)
sheet = parser.parseFile(css_file)
for rule in sheet:
if isinstance(rule, cssutils.css.CSSStyleRule):
base_selectors = extract_base_selector(rule.selectorText)
if base_selectors and not any(sel in used_classes for sel in base_selectors):
unused_rules.append((css_file, rule.selectorText))
except Exception:
pass # Suppress any errors during processing
return unused_rules
def generate_report(unused_rules: List[Tuple[str, str]]):
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
report_filename = f"unused_css_report_{timestamp}.txt"
with open(report_filename, 'w') as report:
report.write(f"Unused CSS Report - {timestamp}\n")
report.write("=" * 40 + "\n\n")
if not unused_rules:
report.write("No unused CSS rules found!\n")
else:
report.write(f"Total Unused Rules: {len(unused_rules)}\n\n")
report.write("Detailed Breakdown:\n")
# Group by CSS file
css_groups = {}
for file, selector in unused_rules:
if file not in css_groups:
css_groups[file] = []
css_groups[file].append(selector)
for css_file, selectors in css_groups.items():
report.write(f"\nCSS File: {css_file}\n")
report.write("-" * 40 + "\n")
for selector in selectors:
report.write(f" {selector}\n")
return report_filename
def main():
html_directory = '/path/to/html/dir'
css_files = [
'/path/to/css/styles.css',
'/path/to/css/bootstrap.css'
]
unused = find_unused_css(html_directory, css_files)
report_file = generate_report(unused)
if __name__ == "__main__":
main()
Check for the report with name unused_css_report_{timestamp}.txt
in the same folder where the Python script is run from. Here is a sample:
Unused CSS Report - 20250801_105330
========================================
Total Unused Rules: 12
Detailed Breakdown:
CSS File: /path/to/css/styles.css
----------------------------------------
.header-section.has-img .no-img
.noted-content
.noted-item
.noted-post-time
.noted-content .noted-item img
.noted-pagination
CSS File: /path/to/css/bootstrap.css
----------------------------------------
.sr-only
.sr-only-focusable:active, .sr-only-focusable:focus
.page-header
.pre-scrollable
.container-fluid
.table
Use the report to help you clean up the CSS. I recommend commenting out at first before totally removing, and test test test!
Have fun.