@use, @forward, SCSS partials (_prefix files), and Tailwind directives are all resolved accurately. No manual configuration needed.
SCSS import resolution
Fallow understands the full SCSS module system. Imports create edges in the module graph, and re-exports propagate through the dependency chain.| Directive | How fallow handles it |
|---|---|
@use 'path' | Resolved as a module import, creating a graph edge |
@forward 'path' | Treated as a re-export in the module graph |
@import 'path' | Legacy import, tracked as a side-effect edge |
@use 'sass:math' | Recognized as a Sass built-in module, not flagged as missing |
@use 'components/button', it resolves through these candidates in order:
components/_button.scss(partial convention)components/button/_index.scss(directory index)components/button/index.scss(plain index)
styles/theme.scss
- Importing the
sass:colorbuilt-in (correctly ignored, not a project dependency) - Importing
./variables, resolved to_variables.scssvia partial convention - Importing
./mixins/responsive, resolved tomixins/_responsive.scss - Re-exporting
$primaryand$secondaryfrom./tokens
SCSS include paths (configured via frameworks like Angular) are also supported. When a bare specifier like
@use 'variables' cannot be resolved locally, fallow searches configured include directories with the same partial and index conventions.CSS Module class tracking
Files with.module.css or .module.scss extensions receive special treatment. Fallow extracts every class name from selectors and exposes them as named exports.
Button.module.css
root, primary, and disabled.
When a component imports a CSS module, fallow tracks which classes are actually accessed:
Button.tsx
styles.root and styles.primary are marked as used. The disabled class is never referenced, so fallow reports it as an unused export.
$ fallow dead-code --unused-exports
Tailwind CSS integration
When fallow encounters@apply or @tailwind directives in any CSS or SCSS file, it creates a synthetic dependency on the tailwindcss package. This prevents false “unused dependency” reports.
globals.css
@tailwind and @apply directives and marks tailwindcss as a used dependency.
@plugin directive (Tailwind v4)
Tailwind v4 moves plugin registration into CSS via the @plugin directive. Fallow extracts each @plugin target as a default import, so package plugins are credited as used dependencies and relative plugin files have their default export marked used.
app.css
@tailwindcss/typography and daisyui as used dependencies, and treats ./tailwind-local-plugin.js as a reachable file whose default export is consumed. The directive is also recognized inside Vue and Svelte <style> blocks. Unlike SCSS @use, extensionless package names such as daisyui stay bare even in .scss and .sass files (they are package specifiers, not local partials).
Unused Tailwind v4 @theme tokens
With fallow health --css, Tailwind v4 projects also get cleanup candidates for @theme design tokens whose generated utility is not referenced anywhere fallow can see.
theme.css
App.tsx
--color-brand is credited because bg-brand uses the generated utility suffix. --shadow-glow is reported in css_analytics.unused_theme_tokens with a read-only verification action.
Fallow also credits var(--token) reads, @apply utilities, arbitrary values such as rounded-[--radius-card], and token-to-token references inside @theme blocks. The check is conservative: it only runs for Tailwind v4 projects, abstains when Tailwind plugins or published CSS surfaces may consume tokens invisibly, and treats every result as a candidate, never as an auto-fix.
Other CSS cleanup signals follow the same contract. css_analytics.unreferenced_css_classes (a plain-CSS class defined but matched by no class/className in project markup), css_analytics.unresolved_class_references (the reverse: a markup class one edit away from a defined class, a likely typo), css_analytics.unused_font_faces, css_analytics.undefined_keyframes, and css_analytics.font_size_unit_mix are advisory candidates with read-only verification actions. They are meant to send a reviewer to the likely stale class, font, animation, or type-scale mismatch without claiming it is always safe to delete.
Token blast-radius (token_consumers)
The inverse of the unused-token signal: instead of asking “is this token dead?”, token_consumers answers “what consumes this token?” so you can see a token’s blast radius before changing it. On Tailwind v4 projects fallow health --css --format json adds css_analytics.token_consumers, a reverse index over the same gated @theme token set:
kind: utility (a markup class ending in -<name>, such as bg-brand), apply (a class inside an @apply body), css-var (a regular-CSS var(--token) read), or theme-var (a var() reference inside another @theme token). consumers is a capped sample; consumer_count is the full total.
consumer_count is a static lower bound: a computed class name like bg-${color} is invisible to the scan, so a 0 here is a “nothing fallow can see consumes this” signal, the same population that surfaces in unused_theme_tokens, not a proof that the token is safe to delete. token_consumers is descriptive context for change decisions; the cleanup candidate stays unused_theme_tokens.
CSS-in-JS tokens
token_consumers also covers CSS-in-JS token DEFINITIONS, so changing a StyleX defineVars, vanilla-extract createTheme / createThemeContract / createGlobalTheme, or PandaCSS defineTokens token shows its blast radius the same way an @theme token does. These tokens are defined in JS objects and consumed through cross-module member access (import { vars } from './tokens'; vars.color.primary) or Panda token('colors.brand') calls, so the entry shape differs by origin:
kind and by the token shape: a Tailwind token’s token is the ---prefixed custom property (--color-brand) with kind theme-var / css-var / utility / apply; StyleX and vanilla-extract tokens use a binding-qualified dotted access path (vars.color.primary) with kind js-member; PandaCSS tokens use the defining binding plus token path (tokens.colors.brand) and token(...) consumers are tagged js-call. The cross-file scan resolves import edges to their defining files before matching, so an unrelated same-named vars from a different module is never counted.
Two caveats are sharper than the Tailwind case. The cross-file scan uses fallow’s shared import resolver, so relative imports, tsconfig paths aliases, and workspace package imports can resolve to the token definition. Dynamic import strings, unresolved aliases, generated package state, and computed token access still keep consumer_count a lower bound. And unlike Tailwind there is no corroborating dead-token finding (no CSS-in-JS analogue of unused_theme_tokens), so a CSS-in-JS consumer_count of 0 is a weaker “fallow sees no consumers” signal, not a deletion verdict. CSS-in-JS token blast-radius is gated on a declared CSS-in-JS library (@stylexjs/stylex, @vanilla-extract/css, or @pandacss/dev).
Tailwind v3 config
The Tailwind plugin provides additional coverage by parsingtailwind.config.{js,ts,cjs,mjs}:
- Content globs from the
contentarray are treated as always-used file patterns - Plugin packages referenced via
require()or string arrays are marked as used dependencies - Presets are tracked as referenced dependencies
- Imports in the config file (e.g.,
import defaultTheme from 'tailwindcss/defaultTheme') are tracked
tailwind.config.js
tailwindcss, @tailwindcss/typography, and @tailwindcss/forms as used dependencies.
CSS @import tracking
Plain CSS @import statements create edges in the module graph. Both relative paths and package imports are tracked.
styles/main.css
./reset.css and ./typography.css as local file imports, and @fontsource/inter as a package dependency. Remote URLs (https://...) and data URIs are correctly ignored.
Styling health grade
On top of the rawcss_analytics, fallow health --css derives a styling health grade: a descriptive A-F letter (and a 0-100 score) for the quality of the CSS itself, scored separately from the JavaScript/TypeScript code health score. It appears as styling_health in JSON and a Styling health: line under CSS health in human output.
| Penalty | What it measures |
|---|---|
duplication | Removable declarations from copy-paste declaration blocks. Down-weighted to a soft hint (exact CSS duplication is the least-harmful pattern: it gzips away and CSS has no native abstraction). |
dead_surface | Unused @theme tokens (as a share of all defined tokens) plus unreferenced classes, unused @property/@layer at-rules, and dead @font-face families. |
broken_references | Markup classes one edit from a defined class, plus animations referencing a @keyframes defined nowhere (likely typos or stale renames). |
token_erosion | Design-token erosion: mixed font-size units, Tailwind arbitrary-value bypasses, and hardcoded value drift (the count of distinct hardcoded box-shadow / border-radius / line-height values above a healthy per-property palette). |
structural | !important density above a healthy floor, plus deep style-rule nesting. |
Value drift over exact repetition
The grade weights design-token drift (inconsistency) more than byte-identical repetition, because divergent values are what design tokens exist to fix. Thetoken_erosion value-drift signal counts only hardcoded literals: a system that references its scale through custom properties (box-shadow: var(--shadow-md)) scores zero drift no matter how many tokens it defines, because var(--*) references and @theme definitions are never counted as distinct hardcoded values. A system that hardcodes many ad-hoc shadows/radii/line-heights accrues a small, gently-growing penalty: a nudge toward tokenizing, never a cliff.
Confidence and scope
A grade computed from a thin CSS surface (few authored declarations, or predominantly compile-time-atomic CSS-in-JS like StyleX/Panda) is markedconfidence: "low" with a stated reason, so a sparse-surface grade is not presented with the same authority as one from a full design system. Confidence is metadata only: it never changes the score.
Styling health is descriptive-only. It never gates an exit code, a badge, or CI, and it never affects the JavaScript/TypeScript health_score. Treat it as a design-system credibility signal, not a pass/fail gate.
formula_version increments whenever the styling-health rubric is recalibrated (independently of the code-health formula version), so you can tell a score shift caused by a weight change apart from one caused by an actual change to your CSS. If you diff styling_health.score / grade over time (snapshot CIs, trend dashboards), gate on formula_version and re-baseline when it bumps.Common patterns that just work
| Pattern | Example | How fallow handles it |
|---|---|---|
| SCSS partial imports | @use 'mixins' | Resolves to _mixins.scss |
| Sass built-in modules | @use 'sass:math' | Correctly ignored, not flagged as missing |
@use with namespace | @use 'colors' as c | Tracked as module import |
@forward with show/hide | @forward 'tokens' show $primary | Re-export with filtering |
| CSS custom properties | var(--color-primary) | File-level tracking (not per-property) |
| PostCSS plugins | postcss.config.js | Plugin dependencies detected via PostCSS plugin config |
| Scoped CSS packages | @import '@company/tokens/base.scss' | Resolved as npm package, not local file |
| SCSS directory index | @use 'components' | Resolves to components/_index.scss |
See also
Dead code analysis
How fallow builds the module graph and detects unused code.
Non-JS file types
All non-JavaScript file types fallow analyzes automatically.
Configuration
Configure entry points, rules, and analysis scope.