CSS Unused Selector Detector - Hooks
Detects unused CSS selectors when stylesheets are modified to keep CSS lean using PurgeCSS, PostCSS, and content analysis. This hook runs on CSS/SCSS file write/edit operations and analyzes stylesheets to identify unused selectors, generate optimized output, and report before/after size metrics.
Open the source and read safety notes before installing.
Schema details
- Install type
- cli
- Reading time
- 1 min
- Difficulty score
- 0
- Troubleshooting
- Yes
- Breaking changes
- No
- Trigger
- PostToolUse
- Script language
- bash
Script body
#!/usr/bin/env bash
# Read the tool input from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# Check if it's a CSS/SCSS file
if [[ "$FILE_PATH" == *.css ]] || [[ "$FILE_PATH" == *.scss ]] || [[ "$FILE_PATH" == *.sass ]]; then
echo "🔍 Analyzing CSS file for unused selectors: $FILE_PATH" >&2
# Check if file exists
if [ ! -f "$FILE_PATH" ]; then
echo "📁 CSS file does not exist yet, skipping analysis" >&2
exit 0
fi
# Get original file size
ORIGINAL_LINES=$(wc -l < "$FILE_PATH" 2>/dev/null || echo "0")
ORIGINAL_SIZE=$(wc -c < "$FILE_PATH" 2>/dev/null || echo "0")
echo "📊 Original CSS: $ORIGINAL_LINES lines, $ORIGINAL_SIZE bytes" >&2
# Try PurgeCSS if available
if command -v npx &> /dev/null && npx purgecss --version &> /dev/null; then
echo "🧹 Running PurgeCSS analysis..." >&2
# Create analysis directory
mkdir -p css-analysis
# Run PurgeCSS with multiple content patterns
if npx purgecss --css "$FILE_PATH" \
--content './src/**/*.{html,js,jsx,ts,tsx,vue,svelte}' \
--content './**/*.{html,js,jsx,ts,tsx,vue,svelte}' \
--output ./css-analysis/ 2>/dev/null; then
# Analyze results
PURGED_FILE="./css-analysis/$(basename "$FILE_PATH")"
if [ -f "$PURGED_FILE" ]; then
PURGED_LINES=$(wc -l < "$PURGED_FILE" 2>/dev/null || echo "0")
PURGED_SIZE=$(wc -c < "$PURGED_FILE" 2>/dev/null || echo "0")
SAVED_LINES=$((ORIGINAL_LINES - PURGED_LINES))
SAVED_SIZE=$((ORIGINAL_SIZE - PURGED_SIZE))
REDUCTION_PERCENT=$((SAVED_SIZE * 100 / ORIGINAL_SIZE))
echo "📉 Optimized CSS: $PURGED_LINES lines, $PURGED_SIZE bytes" >&2
echo "✅ Potential savings: $SAVED_LINES lines, $SAVED_SIZE bytes ($REDUCTION_PERCENT% reduction)" >&2
echo "📁 Check css-analysis/$(basename "$FILE_PATH") for optimized version" >&2
else
echo "⚠️ PurgeCSS analysis completed but no output generated" >&2
fi
else
echo "❌ PurgeCSS analysis failed - check content paths" >&2
fi
else
echo "💡 Install PurgeCSS (npm install -g purgecss) for CSS optimization analysis" >&2
# Basic analysis without PurgeCSS
SELECTOR_COUNT=$(grep -o '[.#][a-zA-Z][-a-zA-Z0-9_]*' "$FILE_PATH" 2>/dev/null | sort -u | wc -l || echo "0")
echo "📊 Found $SELECTOR_COUNT unique CSS selectors in file" >&2
fi
echo "✅ CSS analysis completed for $FILE_PATH" >&2
else
echo "File $FILE_PATH is not a CSS/SCSS file, skipping analysis" >&2
fi
exit 0Full copyable content
{
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/css-unused-selector-detector.sh",
"matchers": [
"write",
"edit"
]
}
}
}About this resource
Features
- Automatic unused CSS selector detection with PurgeCSS ^6.0.0+ for comprehensive content scanning across multiple file types and frameworks
- Support for CSS, SCSS, SASS, and modern CSS frameworks (Tailwind CSS, Bootstrap, Material UI) with framework-specific optimization
- Content analysis across HTML, JS, JSX, TS, TSX, Vue, and Svelte files using multiple content path patterns for accurate selector detection
- Before/after comparison with line count reduction and byte size savings calculation showing percentage reduction metrics
- Optimized CSS output generation with safelist support for critical framework classes preventing removal of essential styles
- Integration with modern build workflows supporting Vite, Webpack, Rollup, and PostCSS pipelines for seamless optimization
- Safelist configuration to protect dynamically-generated classes and framework utilities from being removed during purging
- Detailed reporting with percentage reduction metrics, file size savings, and optimized output file generation for review
Use Cases
- Automatic CSS optimization during stylesheet development providing real-time feedback on unused selectors as CSS is modified
- Dead code elimination in large CSS codebases identifying and removing unused styles to reduce bundle size and improve performance
- Performance optimization for web applications reducing CSS file size for faster page loads and improved Core Web Vitals
- CSS bundle size reduction in production builds automatically removing unused styles before deployment to minimize asset size
- Maintenance of clean, lean stylesheets keeping CSS files organized and free of dead code during long-term project maintenance
- Framework migration assistance identifying unused framework classes when migrating between CSS frameworks or design systems
Installation
- Create hooks directory: mkdir -p .claude/hooks
- Create hook file: touch .claude/hooks/css-unused-selector-detector.sh
- Make executable: chmod +x .claude/hooks/css-unused-selector-detector.sh
- Add configuration from Hook Configuration section above to .claude/settings.json or ~/.claude/settings.json
- Alternative: Use the interactive /hooks command in Claude Code
Config paths
- Local (not committed):
.claude/settings.local.json - User settings (global):
~/.claude/settings.json - Project-wide (committed):
.claude/settings.json
Requirements
- Claude Code CLI installed
- Project directory initialized
- Bash shell available
- jq installed (for JSON parsing from stdin)
- Optional: PurgeCSS ^6.0.0 (npm install -g purgecss or npx purgecss) for unused CSS detection, PostCSS ^8.4.0+ for CSS processing
- Node.js 18+ and npm/yarn/pnpm (for installing and running PurgeCSS and PostCSS, required for CSS optimization workflows)
Hook Configuration
{
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/css-unused-selector-detector.sh",
"matchers": ["write", "edit"]
}
}
}
Hook Script
#!/usr/bin/env bash
# Read the tool input from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# Check if it's a CSS/SCSS file
if [[ "$FILE_PATH" == *.css ]] || [[ "$FILE_PATH" == *.scss ]] || [[ "$FILE_PATH" == *.sass ]]; then
echo "🔍 Analyzing CSS file for unused selectors: $FILE_PATH" >&2
# Check if file exists
if [ ! -f "$FILE_PATH" ]; then
echo "📁 CSS file does not exist yet, skipping analysis" >&2
exit 0
fi
# Get original file size
ORIGINAL_LINES=$(wc -l < "$FILE_PATH" 2>/dev/null || echo "0")
ORIGINAL_SIZE=$(wc -c < "$FILE_PATH" 2>/dev/null || echo "0")
echo "📊 Original CSS: $ORIGINAL_LINES lines, $ORIGINAL_SIZE bytes" >&2
# Try PurgeCSS if available
if command -v npx &> /dev/null && npx purgecss --version &> /dev/null; then
echo "🧹 Running PurgeCSS analysis..." >&2
# Create analysis directory
mkdir -p css-analysis
# Run PurgeCSS with multiple content patterns
if npx purgecss --css "$FILE_PATH" \
--content './src/**/*.{html,js,jsx,ts,tsx,vue,svelte}' \
--content './**/*.{html,js,jsx,ts,tsx,vue,svelte}' \
--output ./css-analysis/ 2>/dev/null; then
# Analyze results
PURGED_FILE="./css-analysis/$(basename "$FILE_PATH")"
if [ -f "$PURGED_FILE" ]; then
PURGED_LINES=$(wc -l < "$PURGED_FILE" 2>/dev/null || echo "0")
PURGED_SIZE=$(wc -c < "$PURGED_FILE" 2>/dev/null || echo "0")
SAVED_LINES=$((ORIGINAL_LINES - PURGED_LINES))
SAVED_SIZE=$((ORIGINAL_SIZE - PURGED_SIZE))
REDUCTION_PERCENT=$((SAVED_SIZE * 100 / ORIGINAL_SIZE))
echo "📉 Optimized CSS: $PURGED_LINES lines, $PURGED_SIZE bytes" >&2
echo "✅ Potential savings: $SAVED_LINES lines, $SAVED_SIZE bytes ($REDUCTION_PERCENT% reduction)" >&2
echo "📁 Check css-analysis/$(basename "$FILE_PATH") for optimized version" >&2
else
echo "⚠️ PurgeCSS analysis completed but no output generated" >&2
fi
else
echo "❌ PurgeCSS analysis failed - check content paths" >&2
fi
else
echo "💡 Install PurgeCSS (npm install -g purgecss) for CSS optimization analysis" >&2
# Basic analysis without PurgeCSS
SELECTOR_COUNT=$(grep -o '[.#][a-zA-Z][-a-zA-Z0-9_]*' "$FILE_PATH" 2>/dev/null | sort -u | wc -l || echo "0")
echo "📊 Found $SELECTOR_COUNT unique CSS selectors in file" >&2
fi
echo "✅ CSS analysis completed for $FILE_PATH" >&2
else
echo "File $FILE_PATH is not a CSS/SCSS file, skipping analysis" >&2
fi
exit 0
Examples
CSS Unused Selector Detector Hook Script
Complete hook script that detects unused CSS selectors when stylesheets are modified
#!/usr/bin/env bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r ".tool_name")
FILE_PATH=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // \"\"")
if [ -z "$FILE_PATH" ]; then exit 0; fi
if [[ "$FILE_PATH" == *.css ]] || [[ "$FILE_PATH" == *.scss ]]; then
ORIGINAL_LINES=$(wc -l < "$FILE_PATH" 2>/dev/null || echo "0")
ORIGINAL_SIZE=$(wc -c < "$FILE_PATH" 2>/dev/null || echo "0")
if command -v npx &> /dev/null && npx purgecss --version &> /dev/null; then
mkdir -p css-analysis
npx purgecss --css "$FILE_PATH" --content "./src/**/*.{html,js,jsx,ts,tsx}" --output ./css-analysis/ 2>/dev/null
PURGED_FILE="./css-analysis/$(basename "$FILE_PATH")"
if [ -f "$PURGED_FILE" ]; then
PURGED_LINES=$(wc -l < "$PURGED_FILE" 2>/dev/null || echo "0")
PURGED_SIZE=$(wc -c < "$PURGED_FILE" 2>/dev/null || echo "0")
SAVED=$((ORIGINAL_SIZE - PURGED_SIZE))
echo "✅ Potential savings: $SAVED bytes" >&2
fi
fi
fi
exit 0
Hook Configuration
Complete hook configuration for .claude/settings.json to enable CSS unused selector detection on file changes
{
"hooks": {
"postToolUse": [
{
"hooks": [
{
"type": "command",
"command": "./.claude/hooks/css-unused-selector-detector.sh",
"matchers": ["write", "edit"]
}
]
}
]
}
}
CSS Analysis with Safelist
Enhanced hook script using PurgeCSS with safelist patterns to protect framework classes
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // \"\"")
if [ -z "$FILE_PATH" ] || [[ ! "$FILE_PATH" == *.{css,scss,sass} ]]; then exit 0; fi
if command -v npx &> /dev/null && npx purgecss --version &> /dev/null; then
mkdir -p css-analysis
npx purgecss --css "$FILE_PATH" \
--content "./src/**/*.{html,js,jsx,ts,tsx,vue,svelte}" \
--content "./app/**/*.{html,js,jsx,ts,tsx}" \
--safelist [/^btn-/, /^nav-/, /^modal-/] \
--output ./css-analysis/ 2>/dev/null
echo "✅ CSS analysis with safelist completed" >&2
fi
exit 0
CSS Analysis with Percentage Reporting
Enhanced hook script with detailed percentage reduction reporting
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // \"\"")
if [ -z "$FILE_PATH" ] || [[ ! "$FILE_PATH" == *.{css,scss} ]]; then exit 0; fi
ORIGINAL_SIZE=$(wc -c < "$FILE_PATH" 2>/dev/null || echo "0")
if command -v npx &> /dev/null && npx purgecss --version &> /dev/null; then
mkdir -p css-analysis
npx purgecss --css "$FILE_PATH" --content "./**/*.{html,js,jsx,ts,tsx}" --output ./css-analysis/ 2>/dev/null
PURGED_FILE="./css-analysis/$(basename "$FILE_PATH")"
if [ -f "$PURGED_FILE" ]; then
PURGED_SIZE=$(wc -c < "$PURGED_FILE" 2>/dev/null || echo "0")
SAVED=$((ORIGINAL_SIZE - PURGED_SIZE))
PERCENT=$((SAVED * 100 / ORIGINAL_SIZE))
echo "📊 Original: $ORIGINAL_SIZE bytes, Optimized: $PURGED_SIZE bytes" >&2
echo "✅ Savings: $SAVED bytes ($PERCENT% reduction)" >&2
fi
fi
exit 0
Basic Selector Count Analysis
Basic hook script that counts CSS selectors without requiring PurgeCSS
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // \"\"")
if [ -z "$FILE_PATH" ] || [[ ! "$FILE_PATH" == *.{css,scss} ]]; then exit 0; fi
SELECTOR_COUNT=$(grep -oE "[.#][a-zA-Z][-a-zA-Z0-9_]*" "$FILE_PATH" 2>/dev/null | sort -u | wc -l || echo "0")
echo "📊 Found $SELECTOR_COUNT unique CSS selectors" >&2
if [ "$SELECTOR_COUNT" -gt 100 ]; then
echo "⚠️ High selector count - consider splitting into smaller files" >&2
fi
exit 0
Troubleshooting
PurgeCSS not detecting any unused selectors in CSS
Verify content paths match your project structure: update --content patterns to include all HTML/JSX/TSX files. Check PurgeCSS config safelist if critical selectors are protected. Ensure content files are accessible: test with ls command. Verify file extensions match: use --content pattern with correct glob syntax.
PostToolUse hook only runs for write, not edit operations
Add both matchers to hook config: matchers array should include write and edit. Verify tool_name extraction from stdin matches expected values. Test with echo command to debug tool input parsing. Check hook configuration in .claude/settings.json.
css-analysis directory not created or files missing
Check write permissions in project root directory. Ensure mkdir -p succeeds without errors. Verify PurgeCSS output path is writable. Check disk space if directory creation fails silently. Test directory creation manually: mkdir -p css-analysis && echo OK.
PurgeCSS removes critical CSS framework classes
Add safelist patterns to PurgeCSS config for framework classes using --safelist flag. Use safelist syntax with regex patterns for framework prefixes. Consider extracting framework CSS to separate file excluded from purging. Use safelist patterns like /^btn-/, /^nav-/ for framework class prefixes.
No output generated message despite PurgeCSS success
Check PURGED_FILE path construction matches PurgeCSS output format. Verify basename command extracts correct filename. Ensure output directory exists before PurgeCSS runs. Check PurgeCSS output format and verify --output flag creates files correctly.
CSS analysis is too slow for large stylesheets
Add timeout wrapper to limit execution time. Limit analysis scope to files under specific size threshold. Use faster content scanning by limiting --content patterns to specific directories. Skip analysis for generated files: exclude node_modules, dist, build directories.
PurgeCSS not finding content files in subdirectories
Use recursive glob patterns with ** syntax for subdirectory scanning. Check file permissions to ensure content files are readable. Verify glob pattern syntax matches your file structure. Use absolute paths if relative paths fail. Check PurgeCSS version and upgrade to latest for improved file discovery.
Percentage calculation shows incorrect reduction values
Verify file size calculation uses wc -c for byte count, not wc -l for lines. Check for division by zero errors before percentage calculation. Use proper integer math for percentage calculation. Test with known values to verify calculation logic.
- Features
- Use Cases
- Installation
- Config paths
- Requirements
- Hook Configuration
- Hook Script
- Examples
- CSS Unused Selector Detector Hook Script
- Hook Configuration
- CSS Analysis with Safelist
- CSS Analysis with Percentage Reporting
- Basic Selector Count Analysis
- Troubleshooting
- PurgeCSS not detecting any unused selectors in CSS
- PostToolUse hook only runs for write, not edit operations
Source citations
Signals
Loading live community signals…
A short, calm digest of reviewed Claude resources. Unsubscribe any time.