Install command
Provided
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.
Source-backed facts for citing this resource, derived directly from the registry — also available as plain text for AI assistants.
Decision playbook
Signals are present but mixed. Use the checklist below to confirm the source and operational safety for your environment.
0
78
—
No baseline selected
No major trust-signal divergence detected in the current selection.
Confirm ownership and provenance before trusting install instructions.
Source link availableRequired
Open the canonical repository and verify ownership.
Source provenance statusRequired
Marked as source-backed.
Metadata reviewed
Registry metadata indicates a reviewed listing.
Validate risk disclosures before installation or API wiring.
Safety notes presentRequired
Review the listed safety guidance before running commands.
Privacy notes presentRequired
Review data handling notes before connecting accounts or secrets.
Trust level risk gateRequired
Trust level does not block evaluation.
Check package metadata and artifact integrity signals.
Install payload available
Install or copy payload is available for review.
Package verification flag
No package verification flag provided.
Checksum metadata
No checksum provided for downloaded artifact.
Use compare context to validate trade-offs before adoption.
Compare tray has multiple entries
Add at least one more entry to compare trust differences.
Baseline comparison available
No baseline peer selected yet.
Diverging trust signals identified
No major trust-signal divergence found.
Setup at a glance
Copy-ready — paste the snippet to get started.
Install command
Provided
Config snippet
Provided
Copy snippet
Provided
Prerequisites
None
Platforms
1 listed
Difficulty
0/100
Adoption plan
Current risk score 16/100. Use staged verification before broader rollout.
Validate source and review signals before any execution.
Confirm source provenanceRequired
Source URL/provenance metadata is present.
Confirm metadata review state
Listing has review metadata.
Verify install payload
Install/config payload exists and can be inspected.
Confirm safety, privacy, and package integrity signals.
Review safety notesRequired
Safety notes are present.
Review privacy notesRequired
Privacy notes are present.
Verify package integrity metadata
No package verification/checksum metadata.
Adopt in controlled steps based on the selected plan.
Run in isolated sandbox firstRequired
Use a constrained sandbox and observe behavior across multiple tasks.
Roll out graduallyRequired
Roll out to a small cohort before wider usage.
Set monitoring and fallback
Define rollback path and monitor errors after adoption.
Evidence readiness
Required evidence gates are covered (5/6 signals complete).
Source repository/provenance is listed.
Required in this preset
Review metadata is present.
Required in this preset
Safety notes are present.
Required in this preset
Privacy notes are present.
Optional in this preset
Package integrity metadata is missing.
Optional in this preset
Install payload is available.
Required in this preset
Required evidence gates are covered for this preset.
Decision timeline
5/6 steps complete with no blocking gaps for this preset.
triage
Source/provenance metadata is available.
triage
Review metadata is available.
verify
Safety notes are available.
verify
Privacy notes are available.
verify
Package integrity metadata is missing.
rollout
Install payload is available.
No required blockers for this timeline preset.
Safety & privacy surface
1 safety and 1 privacy notes across 2 risk areas. Review closely: credentials & tokens, permissions & scopes.
#!/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{
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/css-unused-selector-detector.sh",
"matchers": [
"write",
"edit"
]
}
}
}.claude/settings.local.json~/.claude/settings.json.claude/settings.json{
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/css-unused-selector-detector.sh",
"matchers": ["write", "edit"]
}
}
}
#!/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
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
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"]
}
]
}
]
}
}
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
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 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
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.
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.
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.
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.
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.
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.
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.
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.
Show that CSS Unused Selector Detector - Hooks is listed on HeyClaude. Paste this Markdown into your README — it renders the badge and links back to this page.
[](https://heyclau.de/entry/hooks/css-unused-selector-detector)CSS Unused Selector Detector - Hooks side by side with 3 alternatives on trust, install, platform support, and disclosed safety notes — all from reviewed registry metadata.
Next steps differ across entries — use the actions in the table below to copy install commands and source links per resource.
| Field | 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 dossier | A PostToolUse hook that reminds you to enable native query logging for PostgreSQL, Prisma, Sequelize, and TypeORM, and flags possible N+1 patterns when you edit SQL or data-access files. Open dossier | A Claude Code SessionEnd hook that scans a project for dead code and writes a report to .claude/reports. It runs available tools per language: ESLint and Knip for JS/TS, autoflake or pylint for Python, Knip for unused npm dependencies, ripgrep for unreferenced src files, and jq to flag zero-coverage files. Open dossier | Alerts when files exceed size thresholds that could impact performance. This PostToolUse hook provides comprehensive file size monitoring when files are created or modified, automatically detecting files that exceed recommended size thresholds for different file types and providing optimization suggestions. Open dossier |
|---|---|---|---|---|
| Next stepsDiffers | ||||
| Trust | ||||
| Review status | ReviewedMaintainer reviewed | ReviewedMaintainer reviewed | ReviewedMaintainer reviewed | ReviewedMaintainer reviewed |
| Package trust | Package not verified | Package not verified | Package not verified | Package not verified |
| Source provenance | Source-backed | Source-backed | Source-backed | Source-backed |
| Submitter | — | — | — | — |
| Install risk | Review first | Review first | Review first | Review first |
| Notes | Safety ✓ Privacy ✓ | Safety ✓ Privacy ✓ | Safety ✓ Privacy ✓ | Safety ✓ Privacy ✓ |
| Brand | — | — | — | — |
| Category | hooks | hooks | hooks | hooks |
| Source | source-backed | source-backed | source-backed | source-backed |
| Author | JSONbored | JSONbored | JSONbored | JSONbored |
| Added | 2025-09-19 | 2025-10-19 | 2025-10-19 | 2025-09-19 |
| Platforms | Claude Code | Claude Code | Claude Code | Claude Code |
| Source repo | — | — | — | — |
| Safety notes | ✓Runs automatically on its configured Claude Code hook event and executes shell logic that can read, modify, or delete files in your project (and may run builds, installs, or network calls); review the script and scope it to expected paths before enabling. | ✓Runs automatically after bash, write, or edit activity and inspects files that look like query, model, repository, DAO, or SQL files. Creates .claude/logs/query-performance.log and appends database command or file-analysis events. Uses grep-based heuristics for query warnings and should not be treated as proof of a performance defect. | ✓Registered as a SessionEnd hook, so it runs automatically every time a Claude Code session ends. Invokes external tools via npx (ts-prune, Knip, eslint) and locally installed binaries (autoflake, pylint, vulture, jq, rg), which can trigger package downloads and execute project tooling. Defaults to DRY_RUN=true and only writes a report; setting DRY_RUN=false is intended to enable destructive cleanup, so review the report before changing it. Creates directories under .claude/backups and .claude/reports in the working tree. | ✓Runs automatically on its configured Claude Code hook event and executes shell logic that can read, modify, or delete files in your project (and may run builds, installs, or network calls); review the script and scope it to expected paths before enabling. |
| Privacy notes | ✓Receives Claude Code hook input (session metadata, file paths, and tool output) and reads local project files; review what the script logs or forwards to external services and keep credentials out of its output. | ✓Reads query-related source files and may print file paths, query patterns, and database command strings to local hook output. Stores analyzed file paths and database command text in .claude/logs/query-performance.log. Database command text may include connection names, database names, or other operational details if typed directly into the command. | ✓Writes a dead-code report containing file paths, export names, and dependency names from the project to .claude/reports/dead-code-report.txt and echoes it to stderr. Running tools via npx may fetch packages from the npm registry over the network. | ✓Receives Claude Code hook input (session metadata, file paths, and tool output) and reads local project files; review what the script logs or forwards to external services and keep credentials out of its output. |
| Prerequisites | — none listed | — none listed | — none listed | — none listed |
| Install | | — | | |
| Config | | | | |
| Citations | ||||
| Claim | Unclaimed | Unclaimed | Unclaimed | Unclaimed |
Source-backed guides for putting this to work.
Fix Claude Code high CPU/memory, hangs, and context bloat with documented commands.
Loading live community signals…
A short, calm digest of reviewed Claude resources. Unsubscribe any time.