# prefer-theme-tokens

oxlint rule that rewrites raw CSS variable references into your theme's named Tailwind CSS
utilities, like `border-(--border)` → `border-border`.

## At a glance

| Autofix | Editor suggestions | Design system           | Options      |
| ------- | ------------------ | ----------------------- | ------------ |
| Yes     | Yes                | Required (`entryPoint`) | `entryPoint` |

## What this rule does

Detects utilities written as a raw CSS-variable reference — `border-(--border)`,
`bg-[var(--primary)]`, `text-(--background)/80` — and rewrites them to the named theme-token utility
when one exists in your design system. So `border-(--border)` becomes `border-border`,
`hover:bg-[var(--primary)]/50` becomes `hover:bg-primary/50`, and `bg-(--red-500)` (where the user
reached past the `--color-` prefix) becomes `bg-red-500`. Both the paren shorthand
(`prefix-(--name)`) and the bracket form (`prefix-[var(--name)]`) are recognized, and opacity
modifiers (`/80`), variants, and `!` (important) are all preserved.

The candidate `${prefix}-${varName}` has to be a real utility in your DS — the rule consults
`cache.isValid(candidate)`. Existing by name is not enough, though: the named token has to MEAN the
same thing. `@theme inline { --color-primary: var(--primary) }` makes `bg-primary` and
`bg-(--primary)` the same declaration, while a literal `--color-primary` alongside an unrelated
`--primary` makes them different colours — and this rule autofixes. So the rewrite is only proposed
when the token's declaration resolves back to the variable you wrote, or when that variable is
defined nowhere (in which case the current declaration is dead CSS and the token can only be an
improvement). Then it skips any candidate that `cache.getNamedEquivalent` would also resolve,
because that case is owned by `no-unnecessary-arbitrary-value` (same CSS, no double-fire).

DS-dependent — requires `settings.tailwindcss.entryPoint`. If the design system can't load, the rule
emits a single fatal `designSystemUnavailable` diagnostic per file instead of silently passing.

## Options

This rule has no per-rule options beyond the standard `entryPoint` override (string, defaults to
`settings.tailwindcss.entryPoint`). Configure the entry point in `settings.tailwindcss.entryPoint`
for the whole project instead of per-rule whenever possible.

## Examples

### ✗ Incorrect

Assuming a shadcn-style theme with tokens like `--border`, `--primary`, `--background`:

```tsx
// Paren shorthand — the canonical case
<div className="border-(--border) bg-(--primary)" />
// → <div className="border-border bg-primary" />

// Bracket form
<div className="bg-[var(--primary)]/50" />
// → <div className="bg-primary/50" />

// Variants, important, opacity modifiers all preserved
<div className="hover:!border-(--border) dark:bg-(--primary)/80" />
// → <div className="hover:!border-border dark:bg-primary/80" />

// Directional sub-utility
<div className="border-l-(--border)" />
// → <div className="border-l-border" />
```

### ✓ Correct

```tsx
<div className="border-border bg-primary" />

<div className="bg-primary/50" />

<div className="hover:!border-border dark:bg-primary/80" />

<div className="border-l-border" />

// Variable with no matching named utility — leave it
<div className="border-(--no-such-token)" />
```

## Interactions with other rules

- **`no-unnecessary-arbitrary-value`**: paired explicitly. When the bracket form is CSS-equivalent
  to a named utility, the other rule wins and this one stays silent (`getNamedEquivalent` guard). So
  `bg-[var(--color-red-500)]` → `bg-red-500` is owned there; `border-(--border)` → `border-border`
  lives here.
- **`enforce-canonical`**: also part of the arbitrary→named trio. It handles cases where
  canonicalization produces a different CSS shape via your tokens. The three rules carve up the
  space so each arbitrary form has exactly one owner.
- **`enforce-consistent-variable-syntax`**: orthogonal — that rule decides between paren and bracket
  forms when you're staying on the CSS-var representation. This one promotes you off CSS-vars onto a
  named token when possible.

## When to disable it

- **You deliberately want to reference variables by name in markup** to make the theme dependency
  explicit at the call site. In that case keep `no-unnecessary-arbitrary-value` on (CSS-equivalent
  cases) but disable this rule.
- **Your design system uses CSS variables that aren't registered as named utilities** in `@theme` —
  the rule won't fire on those, but if your codebase mixes the two patterns and you don't want
  gradual pressure to expose more tokens, disable it.
