Design Token Lint
GitHub repository

Type to search...

to open search from anywhere

Known Limitations

Inherent limitations of the static analysis approach used by design-token-lint.

design-token-lint uses regex-based static analysis to extract class names from source files. This approach is fast and requires no build step, but it cannot evaluate code at runtime. As a result, some dynamic patterns are not correctly analyzed.

The set of attribute names and utility functions scanned is configurable via classAttributes and classFunctions in your config file. By default, className, class, cn, clsx, classNames, and twMerge are scanned.

These are not bugs — they are inherent to static analysis.

Supported: Multiline className

Multiline className values are supported:

<div
  className="
    p-4
    bg-gray-500
  "
>

Classes spread across multiple lines are extracted correctly.

Supported: Object Keys in class:list

Astro's class:list object syntax is supported — class names in quoted keys are extracted:

<div class:list={[{ "p-4": true, "m-8": isActive }]}>

Both p-4 and m-8 are extracted and linted.

Supported: Multiline Function Calls

cn(), clsx(), classNames(), and twMerge() calls that span multiple lines are supported — arguments are accumulated until the parentheses balance, the same way multiline className values are:

<div
  className={cn(
    "p-4",
    "bg-gray-500",
    isActive && "m-8",
  )}
>

All three string arguments are extracted and linted. This also applies to class:list={[...]} arrays that span multiple lines.

Limitation: Vue/Svelte Dynamic Class Bindings

Static class="..." attributes are extracted correctly in any template language, including Vue and Svelte. Dynamic binding syntax is not:

<!-- Not linted correctly — extracted as garbage tokens, no real violations reported -->
<div :class="isActive ? 'p-4' : 'm-8'">
<div v-bind:class="{ 'p-4': isActive }">
<!-- Not linted at all — the `class:` directive isn't recognized, nothing is extracted -->
<div class:active={isActive} class:p-4={isBig}>

Vue's :class/v-bind:class bindings match the class attribute pattern (the leading : doesn't block the match), so their expression content is extracted and split into tokens — but the result is punctuation-laden garbage (isActive, ?, 'p-4', :, 'm-8') that never matches a prohibited pattern, so violations inside it are silently missed. Svelte's class:name directive isn't matched at all (class isn't followed by =), so it's skipped entirely — also with no violations reported.

Workaround: Extract the relevant classes into a static string (e.g. via a computed property or a cn()/clsx() call assigned to a variable) so the linter sees a plain string literal, or add an ignore comment to acknowledge the gap.

Limitation: Conditional Expressions

Ternary expressions inside className are not extracted:

// Not linted — classes inside ternaries are silently skipped
<div className={isActive ? "p-4" : "m-8"}>

The extractor looks for string literals directly assigned to className or class. The ternary syntax is not matched, so no classes are extracted — and no violations are reported.

This means the linter will silently miss prohibited classes inside ternaries rather than producing false positives. There is no workaround for making the linter check inside ternaries. If you need to ensure these classes are linted, extract them into static variables:

// These static strings are linted
const activeClass = "p-hgap-sm";
const inactiveClass = "m-vgap-md";
<div className={isActive ? activeClass : inactiveClass}>

Limitation: Template Interpolation

Template literals with dynamic expressions produce garbled class names that never match any linting rules:

// Not linted — `p-${size}` is extracted as a literal string, matches no rules
<div className={`p-${size} bg-${color}-500`}>

The extractor captures the raw template content including ${...} expressions. The resulting strings (p-${size}, bg-${color}-500) don't match any pattern, so violations inside them are never reported.

Workaround: Add an ignore comment or refactor to use static class names:

// Use ignore comment to acknowledge this is intentional
{/* design-token-lint-ignore */}
<div className={`p-${size}`}>

Limitation: Escaped Quotes

Class attributes containing escaped quotes may extract incorrectly:

// May extract incorrectly
<div className="p-4 \"m-8\"">

The regex parser does not handle escaped quote sequences inside string literals.

Workaround: Avoid escaped quotes inside class attributes. Use JSX expression syntax instead:

<div className={'p-4 m-8'}>

Limitation: CSS/SCSS Scanning (v1, Declaration-Based Only)

The opt-in css config section scans plain CSS/SCSS declaration values (zIndex, colorLiterals) using the same line/regex-based approach as the Tailwind extractor — no PostCSS, no AST. It correctly handles /* */ comments (including multi-line spans), string literals, url(...) values, and SCSS // line comments. The following are known false negatives in v1 — a literal is present but not flagged, by design:

/* Not flagged — custom-property and SCSS-variable definitions are treated as
   token-definition sites, not usage sites */
:root {
  --brand: #f00;
}
.a {
  $brand: #f00;
}

/* Not flagged — SCSS maps and other nested/interpolated SCSS constructs */
.a {
  $colors: (primary: #f00, secondary: #0f0);
}

Distinguishing a palette-definition zone (:root, @theme, a SCSS variable declaration) from a semantic-token usage site that should reference the palette instead needs zone-aware scanning — a deferred later pass, not part of v1. Values split across exotic multi-line syntax beyond a single declaration are also not guaranteed to be caught.

This is declaration-value scanning only — it says nothing about Tailwind utility classes inside <style> blocks or CSS-in-JS template literals; those remain outside the css section's scope entirely (the Tailwind class extractor doesn't scan .css/.scss files, and the CSS declaration rules don't scan .tsx/.jsx files).

Summary

PatternSupported
Multiline classNameYes
Multiline cn()/clsx()/class:list callsYes
Static string literalsYes
Template literals (static only)Yes
Object keys in class:listYes
Custom attribute names (via classAttributes)Yes — configurable
Custom utility functions (via classFunctions)Yes — configurable
Vue/Svelte static class="..." attributesYes
CSS/SCSS declaration scanning (opt-in, css section)Yes — declaration-based only, see above
Ternary expressionsNo — silently skipped
Template literals with interpolationNo — dynamic parts not linted
Escaped quotes in class stringsNo — may extract incorrectly
Vue :class/v-bind:class bindingsNo — extracted as garbage tokens
Svelte class:name directivesNo — not recognized at all
CSS custom-property/SCSS-variable definitions holding a literalNo — token-definition zone awareness deferred
SCSS maps and nested/interpolated constructsNo — out of scope for v1

Revision History

CreatedUpdated