> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fallow.tools/llms.txt
> Use this file to discover all available pages before exploring further.

# CSS, SCSS, and Tailwind analysis

> How fallow tracks CSS imports, SCSS partials, Tailwind dependencies, and CSS Module class names using AST-based extraction. Zero false positives on @use, @forward, and partial imports.

Fallow tracks CSS and SCSS files using dedicated extraction, not regex pattern matching against source text. `@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 |

SCSS partial resolution follows the standard convention. When fallow encounters `@use 'components/button'`, it resolves through these candidates in order:

1. `components/_button.scss` (partial convention)
2. `components/button/_index.scss` (directory index)
3. `components/button/index.scss` (plain index)

```scss styles/theme.scss theme={null}
@use 'sass:color';
@use 'variables';
@use 'mixins/responsive';

@forward 'tokens' show $primary, $secondary;
```

Fallow sees this file as:

* Importing the `sass:color` built-in (correctly ignored, not a project dependency)
* Importing `./variables`, resolved to `_variables.scss` via partial convention
* Importing `./mixins/responsive`, resolved to `mixins/_responsive.scss`
* Re-exporting `$primary` and `$secondary` from `./tokens`

<Note>
  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.
</Note>

## 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.

```css Button.module.css theme={null}
.root {
  display: flex;
  align-items: center;
}

.primary {
  background: var(--color-primary);
}

.disabled {
  opacity: 0.5;
  pointer-events: none;
}
```

This file exports three named symbols: `root`, `primary`, and `disabled`.

When a component imports a CSS module, fallow tracks which classes are actually accessed:

```tsx Button.tsx theme={null}
import styles from './Button.module.css';

export const Button = ({ variant }: Props) => (
  <button className={`${styles.root} ${styles.primary}`}>
    Click me
  </button>
);
```

Here, `styles.root` and `styles.primary` are marked as used. The `disabled` class is never referenced, so fallow reports it as an unused export.

```bash title="$ fallow dead-code --unused-exports" theme={null}
● Unused exports (1)
  src/Button.module.css
    :3 disabled
  Exported symbols with zero references: https://docs.fallow.tools/explanations/dead-code#unused-exports

✗ 1 issue (0.02s)
```

<Tip>
  Start with `fallow dead-code --unused-exports` to see which CSS module class names are unused across your project.
</Tip>

## 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.

```css globals.css theme={null}
@tailwind base;
@tailwind components;
@tailwind utilities;

.btn {
  @apply px-4 py-2 rounded-md font-medium;
}
```

Fallow detects both `@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.

```css app.css theme={null}
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "daisyui" {
  themes: light --default;
}
@plugin "./tailwind-local-plugin.js";
```

Fallow marks `@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.

```css theme.css theme={null}
@import "tailwindcss";

@theme {
  --color-brand: #f05a28;
  --shadow-glow: 0 0 8px red;
}
```

```tsx App.tsx theme={null}
export const App = () => <div className="bg-brand" />;
```

In this example, `--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:

```json theme={null}
{
  "token": "--color-brand",
  "namespace": "color",
  "definition_path": "src/theme.css",
  "definition_line": 4,
  "consumer_count": 12,
  "consumers": [
    { "path": "src/Button.tsx", "line": 7, "kind": "utility" },
    { "path": "src/theme.css", "line": 9, "kind": "theme-var" }
  ]
}
```

Each consumer location carries a `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:

```json theme={null}
{
  "token": "vars.color.primary",
  "namespace": "vars",
  "definition_path": "src/tokens.stylex.ts",
  "definition_line": 3,
  "consumer_count": 4,
  "consumers": [
    { "path": "src/Card.tsx", "line": 12, "kind": "js-member" }
  ]
}
```

The origins are disambiguated by the consumer `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 parsing `tailwind.config.{js,ts,cjs,mjs}`:

* **Content globs** from the `content` array 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

```js tailwind.config.js theme={null}
import defaultTheme from 'tailwindcss/defaultTheme';

export default {
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
  plugins: [
    require('@tailwindcss/typography'),
    require('@tailwindcss/forms'),
  ],
};
```

Fallow marks `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.

```css styles/main.css theme={null}
@import './reset.css';
@import './typography.css';
@import '@fontsource/inter/400.css';
```

Fallow resolves `./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 raw `css_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.

```json theme={null}
"styling_health": {
  "formula_version": 3,
  "score": 96.0,
  "grade": "A",
  "penalties": { "duplication": 0.0, "dead_surface": 0.0, "broken_references": 0.0, "token_erosion": 4.0, "structural": 0.0 },
  "confidence": "high",
  "confidence_reason": null
}
```

The score starts at 100 and subtracts five capped penalties:

| 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. The `token_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 marked `confidence: "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.

<Note>
  `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.
</Note>

## 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

<CardGroup cols={3}>
  <Card title="Dead code analysis" icon="skull-crossbones" href="/analysis/dead-code">
    How fallow builds the module graph and detects unused code.
  </Card>

  <Card title="Non-JS file types" icon="file-code" href="/analysis/file-types">
    All non-JavaScript file types fallow analyzes automatically.
  </Card>

  <Card title="Configuration" icon="gear" href="/configuration/overview">
    Configure entry points, rules, and analysis scope.
  </Card>
</CardGroup>
