Skip to content

0xstern/tailwind-resolver

Repository files navigation

Tailwind Theme Resolver

TypeScript type generation and runtime resolution for Tailwind CSS v4 theme variables.

Transform your Tailwind v4 CSS variables into fully-typed TypeScript interfaces and runtime objects with automatic conflict detection, intelligent nesting, and comprehensive diagnostics.

Build Status Latest Release License X (formerly Twitter) Follow

Table of Contents

Quick Start

1. Install:

npm install -D tailwind-resolver

2. Configure Vite plugin:

// vite.config.ts

import tailwindcss from '@tailwindcss/vite';
import { tailwindResolver } from 'tailwind-resolver/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    tailwindcss(),
    tailwindResolver({
      input: 'src/styles.css', // Your Tailwind CSS file
    }),
  ],
});

3. Use the generated theme:

import { tailwind } from './generated/tailwindcss';

// Fully typed with autocomplete
const primaryColor = tailwind.variants.default.colors.primary[500];
const darkBackground = tailwind.variants.dark.colors.background;

That's it! The plugin automatically generates TypeScript types and runtime objects from your Tailwind CSS variables.

Core Concepts

What It Does

Tailwind Theme Resolver parses your Tailwind CSS v4 files and generates:

  • TypeScript Types - Full type safety with autocomplete for all theme properties
  • Runtime Objects - JavaScript objects mirroring your CSS variables for use in Canvas, Chart.js, etc.
  • Diagnostic Reports - Automatic detection of CSS conflicts and unresolved variables
  • Theme Variants - Support for dark mode, custom themes, and CSS selector-based variants

Theme Structure

All Tailwind CSS v4 namespaces are supported:

{
  colors: {},           // --color-*
  spacing: {},          // --spacing-* (with dynamic calc helper)
  fonts: {},            // --font-*
  fontSize: {},         // --text-*
  fontWeight: {},       // --font-weight-*
  tracking: {},         // --tracking-*
  leading: {},          // --leading-*
  breakpoints: {},      // --breakpoint-*
  containers: {},       // --container-*
  radius: {},           // --radius-*
  shadows: {},          // --shadow-*
  insetShadows: {},     // --inset-shadow-*
  dropShadows: {},      // --drop-shadow-*
  textShadows: {},      // --text-shadow-*
  blur: {},             // --blur-*
  perspective: {},      // --perspective-*
  aspect: {},           // --aspect-*
  ease: {},             // --ease-*
  animations: {},       // --animate-*
  defaults: {},         // --default-*
  keyframes: {}         // @keyframes
}

Generated Files

With default configuration, the plugin generates:

src/generated/tailwindcss/
├── types.ts           # TypeScript interfaces
├── theme.ts           # Runtime theme objects
├── index.ts           # Convenient re-exports
├── conflicts.md       # CSS conflict report (if conflicts detected)
├── conflicts.json     # Machine-readable conflict data
├── unresolved.md      # Unresolved variable report (if any detected)
└── unresolved.json    # Machine-readable unresolved data

Installation

# Bun
bun add -D tailwind-resolver

# pnpm
pnpm add -D tailwind-resolver

# Yarn
yarn add -D tailwind-resolver

# npm
npm install -D tailwind-resolver

Usage

Vite Plugin (Build-Time Generation)

Recommended for most projects. Generates types once during build, with hot-reload during development.

Basic Configuration:

import tailwindcss from '@tailwindcss/vite';
import { tailwindResolver } from 'tailwind-resolver/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    tailwindcss(),
    tailwindResolver({
      input: 'src/styles.css',
    }),
  ],
});

Full Configuration:

tailwindResolver({
  // Required: Path to CSS file (relative to Vite project root)
  input: 'src/styles.css',

  // Optional: Output directory (default: auto-detected)
  outputDir: 'src/generated/tailwindcss',

  // Optional: Resolve @import statements (default: true)
  resolveImports: true,

  // Optional: Runtime generation control (default: true)
  generateRuntime: {
    variants: true, // Include theme variants
    selectors: true, // Include CSS selectors
    files: false, // Exclude file list (production)
    variables: false, // Exclude raw variables (production)
    reports: {
      conflicts: true, // Generate conflict reports
      unresolved: true, // Generate unresolved variable reports
    },
  },

  // Optional: Control Tailwind defaults (default: true)
  includeDefaults: true,

  // Optional: Nesting configuration
  nesting: {
    colors: { maxDepth: 2 },
    default: { maxDepth: 3 },
  },

  // Optional: Theme overrides
  overrides: {
    '*': { 'fonts.sans': 'Inter, sans-serif' },
    dark: { 'colors.background': '#000000' },
  },

  // Optional: Debug logging (default: false)
  debug: false,
});

Usage in Code:

import { dark, defaultTheme, tailwind } from './generated/tailwindcss';

// Use the master tailwind object
const primary = tailwind.variants.default.colors.primary[500];
const darkBg = tailwind.variants.dark.colors.background;

// Or use individual variant exports
const primary2 = defaultTheme.colors.primary[500];
const darkBg2 = dark.colors.background;

Runtime API (Dynamic Resolution)

For dynamic scenarios like CLI tools, server-side rendering, or runtime theme switching.

Type-Safe Usage (Recommended):

import type { Tailwind } from './generated/tailwindcss';

import { resolveTheme } from 'tailwind-resolver';

const result = await resolveTheme<Tailwind>({
  input: './src/styles.css',
});

// Fully typed with autocomplete
result.variants.default.colors.primary[500];
result.variants.dark.colors.background;
result.selectors.dark; // '[data-theme="dark"]'

Note: Generate types using the Vite plugin or CLI before using the runtime API for type safety.

Basic Usage:

import { resolveTheme } from 'tailwind-resolver';

const result = await resolveTheme({
  input: './src/styles.css',
});

console.log(result.variants.default.colors.primary[500]);
console.log(result.variants.dark.colors.background);

Full Configuration:

const result = await resolveTheme({
  // Option 1: CSS file path
  input: './src/styles.css',

  // Option 2: Raw CSS content
  // css: '@theme { --color-primary: blue; }',

  // Optional: Base path for @import resolution
  basePath: process.cwd(),

  // Optional: Resolve @import statements (default: true)
  resolveImports: true,

  // Optional: Control Tailwind defaults (default: true)
  includeDefaults: true,

  // Optional: Nesting configuration
  nesting: {
    colors: { maxDepth: 2 },
    default: { maxDepth: 3 },
  },

  // Optional: Theme overrides
  overrides: {
    default: { 'fonts.sans': 'Inter' },
  },

  // Optional: Debug logging (default: false)
  debug: false,
});

CLI

Generate types without a build tool:

Basic Usage:

bunx tailwind-resolver -i src/styles.css

Common Options:

# Specify output directory
bunx tailwind-resolver -i src/styles.css -o src/generated

# Types only (no runtime objects)
bunx tailwind-resolver -i src/styles.css --no-runtime

# Include only specific Tailwind defaults
bunx tailwind-resolver -i src/styles.css --include-defaults colors,spacing

# Exclude specific Tailwind defaults
bunx tailwind-resolver -i src/styles.css --exclude-defaults shadows,animations

# Generate only conflict reports
bunx tailwind-resolver -i src/styles.css --reports conflicts

# Debug mode
bunx tailwind-resolver -i src/styles.css --debug

All Options:

  -i, --input <path>              CSS input file (required)
  -o, --output <path>             Output directory (default: auto-detected)
  -r, --runtime                   Generate runtime objects (default: true)
  --no-runtime                    Types only
  --include-defaults [categories] Include only specified Tailwind defaults (comma-separated)
  --exclude-defaults [categories] Exclude specified Tailwind defaults (comma-separated)
  --reports [categories]          Generate only specified reports (conflicts, unresolved)
  --exclude-reports [categories]  Exclude specified reports (comma-separated)
  -d, --debug                     Enable debug mode
  -h, --help                      Show help

Configuration

Tailwind Defaults

Control which Tailwind CSS default theme values are included.

Include All Defaults (Default):

includeDefaults: true;

Exclude All Defaults:

includeDefaults: false;

Selective Inclusion:

includeDefaults: {
  colors: true,       // Include default colors
  spacing: true,      // Include default spacing
  fonts: true,        // Include default fonts
  fontSize: true,     // Include default font sizes
  fontWeight: true,   // Include default font weights
  tracking: false,    // Exclude tracking
  leading: false,     // Exclude leading
  shadows: false,     // Exclude shadows
  animations: false,  // Exclude animations
  // ... 21 categories total
}

Disable Specific Defaults via CSS:

Use initial keyword in @theme blocks (Tailwind v4 docs):

@theme {
  /* Remove specific values */
  --color-lime-*: initial;
  --spacing-4: initial;

  /* Remove entire categories */
  --color-*: initial;
  --spacing-*: initial;

  /* Custom values are preserved */
  --color-primary-500: #3b82f6;
}

Combining Approaches:

// Configuration: Include colors and spacing
includeDefaults: {
  colors: true,
  spacing: true,
  shadows: false,
}

// CSS: Remove specific colors
@theme {
  --color-lime-*: initial;    // Excludes lime from included colors
  --color-fuchsia-*: initial; // Excludes fuchsia from included colors
}

// Result: All default colors EXCEPT lime and fuchsia

Priority: CSS initial declarations take precedence over includeDefaults configuration.

Nesting Configuration

Control how CSS variable names are parsed into nested theme structures.

Default Behavior:

Without configuration, all dashes create nesting levels:

--color-tooltip-outline-50: #fff;
/* → colors.tooltip.outline[50] */

Limit Nesting Depth:

nesting: {
  colors: { maxDepth: 2 },
  // --color-brand-primary-hover-500
  // → colors.brand.primaryHover500 (2 levels, rest flattened to camelCase)
}

Flatten Mode:

Control how parts beyond maxDepth are flattened:

nesting: {
  colors: {
    maxDepth: 2,
    flattenMode: 'camelcase', // Default: blueSkyLight50
    // flattenMode: 'literal',  // Alternative: 'blue-sky-light-50'
  }
}

Consecutive Dashes Handling:

nesting: {
  colors: {
    consecutiveDashes: 'exclude',   // Default: skip variables with --
    // consecutiveDashes: 'nest',     // Treat -- as single -
    // consecutiveDashes: 'camelcase',// Convert to camelCase
    // consecutiveDashes: 'literal',  // Preserve dash
  }
}

Per-Namespace Configuration:

nesting: {
  default: { maxDepth: 1 },         // Apply to all namespaces
  colors: { maxDepth: 3 },          // Override for colors
  shadows: { maxDepth: 2 },         // Override for shadows
  radius: { maxDepth: 0 },          // Completely flat
}

Complete Example:

nesting: {
  colors: {
    maxDepth: 2,
    flattenMode: 'literal',
    consecutiveDashes: 'camelcase',
  }
}

// CSS: --color-tooltip--outline-hover-50
// Step 1: tooltipOutline (consecutive dashes → camelCase)
// Step 2: maxDepth: 2 → colors.tooltipOutline.hover['50']

See Nesting Configuration Details for comprehensive documentation.

Theme Overrides

Apply programmatic overrides to theme values without modifying CSS files.

Use Cases:

  • Inject external variables (Next.js fonts, plugin variables)
  • Fix variant-specific values
  • Global customization across all variants
  • Quick prototyping

Flat Notation:

overrides: {
  default: {
    'colors.primary.500': '#custom-blue',
    'radius.lg': '0.5rem',
  },
  dark: {
    'colors.background': '#000000',
  },
  '*': { // Wildcard - applies to all variants
    'fonts.sans': 'Inter, sans-serif',
  }
}

Nested Notation:

overrides: {
  default: {
    colors: {
      primary: {
        500: '#custom-blue'
      }
    },
    radius: {
      lg: '0.5rem'
    }
  }
}

Detailed Control:

overrides: {
  dark: {
    'radius.lg': {
      value: '0',
      force: true,        // Apply even for low-confidence conflicts
      resolveVars: false  // Skip variable resolution
    }
  }
}

Selector Matching:

overrides: {
  'dark': {},                        // Variant name (preferred)
  '[data-theme="dark"]': {},         // CSS selector (verbose)
  'default': {},                     // Default theme
  'base': {},                        // Alias for 'default'
  '*': {},                           // All variants
  'themeInter': {},                  // .theme-inter → themeInter (camelCase)
}

Note: Multi-word variant names are automatically converted to camelCase:

  • CSS: .theme-noto-sans → Override key: 'themeNotoSans'

See Theme Overrides Details for comprehensive documentation.

Report Generation

Control diagnostic report generation.

Enable All Reports (Default):

generateRuntime: {
  reports: true,
}

Disable All Reports:

generateRuntime: {
  reports: false,
}

Granular Control:

generateRuntime: {
  reports: {
    conflicts: true,    // CSS conflict reports
    unresolved: false,  // Unresolved variable reports
  }
}

CLI:

# Disable all reports
bunx tailwind-resolver -i src/styles.css --no-reports

# Generate only conflict reports
bunx tailwind-resolver -i src/styles.css --reports conflicts

# Exclude unresolved variable reports
bunx tailwind-resolver -i src/styles.css --exclude-reports unresolved

Advanced Features

CSS Conflict Detection

Automatically detects when CSS rules override CSS variables and ensures runtime theme matches actual rendered styles.

Problem:

.theme-mono {
  --radius-lg: 0.45em; /* CSS variable */

  .rounded-lg {
    border-radius: 0; /* CSS rule - overrides the variable! */
  }
}

Solution:

The resolver:

  1. Detects all conflicts between CSS rules and variables
  2. Applies high-confidence overrides automatically
  3. Reports complex cases for manual review

Confidence Levels:

  • High (auto-applied): Static values, simple selectors
  • Medium/Low (manual review): Dynamic values, pseudo-classes, media queries, complex selectors

Generated Reports:

src/generated/tailwindcss/
├── conflicts.md    # Human-readable report with recommendations
└── conflicts.json  # Machine-readable for CI/CD

Terminal Output:

✓ Theme types generated successfully

Generated files:
  - src/generated/tailwindcss/types.ts
  - src/generated/tailwindcss/theme.ts
  - src/generated/tailwindcss/index.ts

⚠  12 CSS conflicts detected (see src/generated/tailwindcss/conflicts.md)

Unresolved Variable Detection

Detects CSS variables with var() references that couldn't be resolved.

Problem:

@theme {
  --font-sans: var(--font-inter); /* Injected by Next.js */
  --color-accent: var(--tw-primary); /* Tailwind plugin variable */
}

Solution:

The resolver categorizes unresolved variables:

  • Unknown - May need definition or verification
  • External - From plugins, frameworks, or external stylesheets
  • Self-referential - Intentionally left unresolved

Generated Reports:

src/generated/tailwindcss/
├── unresolved.md    # Human-readable with actionable recommendations
└── unresolved.json  # Machine-readable for CI/CD

Terminal Output:

ℹ  8 unresolved variables detected (see src/generated/tailwindcss/unresolved.md)

Dynamic Spacing Helper

The spacing property is both an object AND a callable function for dynamic calculations.

Static Values:

defaultTheme.spacing.xs; // '0.75rem'
defaultTheme.spacing.base; // '0.25rem'

Dynamic Calculations:

defaultTheme.spacing(4); // 'calc(0.25rem * 4)' → 1rem
defaultTheme.spacing(16); // 'calc(0.25rem * 16)' → 4rem
defaultTheme.spacing(-2); // 'calc(0.25rem * -2)' → -0.5rem

Usage:

<div style={{
  padding: defaultTheme.spacing(4),   // Same as Tailwind's p-4
  margin: defaultTheme.spacing(-2),   // Same as Tailwind's -m-2
  width: defaultTheme.spacing(64),    // Same as Tailwind's w-64
}} />

Why: Tailwind generates utilities like p-4, m-8, w-16 using calc(var(--spacing) * N). This helper replicates that behavior for runtime use.

Tailwind Utilities Using Spacing:

  • Layout: inset-<n>, m-<n>, p-<n>, gap-<n>
  • Sizing: w-<n>, h-<n>, min-w-<n>, max-w-<n>
  • Typography: indent-<n>, border-spacing-<n>, scroll-m-<n>

Note: Requires --spacing-base in your CSS theme.

Type Safety

Full TypeScript type safety with the generated Tailwind interface.

Generated Constant:

import { tailwind } from './generated/tailwindcss';

// Fully typed with autocomplete
tailwind.variants.default.colors.primary[500]; // ✓
tailwind.variants.dark.colors.background; // ✓
tailwind.selectors.dark; // ✓

Runtime API:

import type { Tailwind } from './generated/tailwindcss';

import { resolveTheme } from 'tailwind-resolver';

const result = await resolveTheme<Tailwind>({
  input: './theme.css',
});

// Same structure, same types
result.variants.default.colors.primary[500]; // ✓
result.variants.dark.colors.background; // ✓
result.selectors.dark; // ✓

Autocomplete: Works automatically when output directory is in tsconfig.json includes.

Examples

Chart.js

import { tailwind } from './generated/tailwindcss';

new Chart(ctx, {
  data: {
    datasets: [
      {
        backgroundColor: [
          tailwind.variants.default.colors.primary[500],
          tailwind.variants.dark.colors.secondary[500],
        ],
      },
    ],
  },
});

Canvas

import { defaultTheme } from './generated/tailwindcss';

ctx.fillStyle = defaultTheme.colors.background;
ctx.font = `${defaultTheme.fontSize.xl.size} ${defaultTheme.fonts.display}`;

Dynamic Theme Switching

import { tailwind } from './generated/tailwindcss';

const currentTheme = isDark
  ? tailwind.variants.dark
  : tailwind.variants.default;

chartInstance.data.datasets[0].backgroundColor =
  currentTheme.colors.primary[500];
chartInstance.update();

Theme Variants

@theme {
  --color-background: #ffffff;
}

[data-theme='dark'] {
  --color-background: #1f2937;
}
import { dark, defaultTheme, selectors } from './generated/tailwindcss';

console.log(defaultTheme.colors.background); // '#ffffff'
console.log(dark.colors.background); // '#1f2937'
console.log(selectors.dark); // "[data-theme='dark']"

Debugging

Enable debug mode to see detailed logging.

Vite:

tailwindResolver({ input: 'src/styles.css', debug: true });

CLI:

bunx tailwind-resolver -i src/styles.css --debug

Runtime API:

resolveTheme({ input: './theme.css', debug: true });

Output:

[Tailwind Theme Resolver] Failed to resolve import: ./components/theme.css
  Resolved path: /Users/you/project/src/components/theme.css
  Error: ENOENT: no such file or directory

[Overrides] Injected variable: --radius-lg = 0.5rem
[Overrides] Applied to 'default': radius.lg = 0.5rem

Requirements

  • Node.js >= 18 or Bun >= 1.0
  • TypeScript >= 5.0 (for type generation)
  • Vite >= 5.0 (for Vite plugin only)

Contributing

Issues and pull requests welcome on GitHub.

License

MIT


Appendix

Nesting Configuration Details

Complete documentation for nesting configuration options.

Default Behavior

Without configuration, every dash creates a nesting level:

@theme {
  --color-tooltip-outline-50: #fff;
  /* → colors.tooltip.outline[50] */

  --shadow-elevation-high-focus: 0 0 0;
  /* → shadows.elevation.high.focus */
}

maxDepth

Limit nesting levels. After the limit, remaining parts are flattened.

Configuration:

nesting: {
  colors: {
    maxDepth: 2;
  }
}

Results:

--color-tooltip-outline-50: #fff;
/* Before: colors.tooltip.outline[50] */
/* After:  colors.tooltip.outline['50'] (no change - only 3 parts) */

--color-brand-primary-hover-500: #3b82f6;
/* Before: colors.brand.primary.hover[500] */
/* After:  colors.brand.primaryHover500 (2 levels, rest flattened) */

Special Case - maxDepth: 0:

nesting: { default: { maxDepth: 0 } }

// --color-brand-primary-dark: #000
// → colors.brandPrimaryDark (completely flat)

consecutiveDashes

Control how consecutive dashes (--) are processed:

Options:

  • 'exclude' (default, matches Tailwind v4) - Skip variables with --
  • 'nest' - Treat -- as single -
  • 'camelcase' - Convert -- to camelCase boundary
  • 'literal' - Preserve -- in keys

Configuration:

nesting: {
  colors: {
    consecutiveDashes: 'camelcase';
  }
}

Results:

--color-button--primary: #fff;

/* 'exclude' (default): Not included */
/* 'nest': colors.button.primary */
/* 'camelcase': colors.buttonPrimary */
/* 'literal': colors['button-'].primary */

flattenMode

Control how parts beyond maxDepth are flattened:

Options:

  • 'camelcase' (default) - Flatten to camelCase
  • 'literal' - Flatten to kebab-case string key

Configuration:

nesting: {
  colors: {
    maxDepth: 2,
    flattenMode: 'literal'
  }
}

Results:

--color-blue-sky-light-50: #e0f2fe;

/* flattenMode: 'camelcase' (default) */
/* → colors.blue.skyLight50 */

/* flattenMode: 'literal' */
/* → colors.blue['sky-light-50'] */

Note: Only applies when maxDepth is reached.

Combining Options

nesting: {
  colors: {
    maxDepth: 2,
    consecutiveDashes: 'camelcase',
    flattenMode: 'literal',
  }
}
--color-tooltip--outline-hover-50: #fff;
/* Step 1: tooltipOutline (consecutive dashes → camelCase) */
/* Step 2: maxDepth: 2 → colors.tooltipOutline.hover['50'] */

Per-Namespace Configuration

nesting: {
  colors: { maxDepth: 3 },      // Deep nesting
  shadows: { maxDepth: 2 },     // Moderate nesting
  spacing: { maxDepth: 1 },     // Flat structure
  radius: { consecutiveDashes: 'camelcase' },
}

Global Default

nesting: {
  default: { maxDepth: 1 },     // Apply to all namespaces
  colors: { maxDepth: 3 },      // Override for colors
}

DEFAULT Key for Conflicts

When both scalar and nested variables exist at the same path, the scalar moves to DEFAULT:

@theme {
  --color-card: blue;
  --color-card-foreground: white;
}
// Result:
{
  colors: {
    card: {
      DEFAULT: 'blue',
      foreground: 'white'
    }
  }
}

Works in Any Order:

/* Nested first, then scalar */
--color-card-foreground: white;
--color-card: blue;
/* Same result: { card: { DEFAULT: 'blue', foreground: 'white' } } */

With Color Scales:

--color-blue-500: #3b82f6;
--color-blue-600: #2563eb;
--color-blue: #1d4ed8;
// Result:
{
  blue: {
    DEFAULT: '#1d4ed8',
    500: '#3b82f6',
    600: '#2563eb'
  }
}

Use Cases

1. Consistent Flat Structure:

nesting: { default: { maxDepth: 0 } }

// All variables flattened:
// --color-brand-primary-500 → colors.brandPrimary500

2. BEM-Style Naming:

nesting: {
  default: {
    maxDepth: 1,
    consecutiveDashes: 'camelcase'
  }
}

// --color-button--primary → colors.buttonPrimary
// --color-input--error → colors.inputError

3. Controlled Depth by Category:

nesting: {
  colors: { maxDepth: 3 },      // Deep color scales
  shadows: { maxDepth: 2 },     // Moderate shadow variants
  radius: { maxDepth: 1 },      // Flat radius values
}

CLI Flags

# Limit nesting depth globally
bunx tailwind-resolver -i src/styles.css --nesting-max-depth 2

# Control consecutive dashes
bunx tailwind-resolver -i src/styles.css --nesting-consecutive-dashes camelcase

# Control flatten mode
bunx tailwind-resolver -i src/styles.css --nesting-flatten-mode literal

# Combine options
bunx tailwind-resolver -i src/styles.css \
  --nesting-max-depth 2 \
  --nesting-consecutive-dashes nest \
  --nesting-flatten-mode literal

Note: CLI flags apply globally to all namespaces. For per-namespace control, use Vite plugin or Runtime API.

Theme Overrides Details

Complete documentation for theme overrides.

When to Use Overrides

  • Inject external variables - Provide values for Next.js fonts, Tailwind plugins, or external sources
  • Fix variant-specific values - Override dark mode or custom theme properties
  • Global customization - Apply consistent values across all variants
  • Quick prototyping - Test theme changes without editing CSS

Syntax Options

Flat Notation (Dot-Separated Paths):

overrides: {
  default: {
    'colors.primary.500': '#custom-blue',
    'radius.lg': '0.5rem',
    'fonts.sans': 'Inter, sans-serif'
  }
}

Nested Notation:

overrides: {
  default: {
    colors: {
      primary: {
        500: '#custom-blue'
      }
    },
    radius: {
      lg: '0.5rem'
    }
  }
}

Mix and Match:

overrides: {
  default: {
    'colors.primary.500': '#custom-blue',
    radius: { lg: '0.5rem' }
  }
}

Selector Matching

Variant Names (Recommended):

overrides: {
  'dark': { 'colors.background': '#000' },
  'themeInter': { 'fonts.sans': 'Inter' },  // .theme-inter → themeInter
}

CSS Selectors (Verbose):

overrides: {
  '[data-theme="dark"]': { 'colors.background': '#000' },
}

Special Keys:

overrides: {
  'default': {},  // Default theme
  'base': {},     // Alias for 'default'
  '*': {},        // All variants (wildcard)
}

Important: Variant names are automatically converted from kebab-case to camelCase:

  • CSS: .theme-inter → Override key: 'themeInter'
  • CSS: .theme-noto-sans → Override key: 'themeNotoSans'

Detailed Control

overrides: {
  dark: {
    'radius.lg': {
      value: '0',
      force: true,        // Apply even for low-confidence conflicts
      resolveVars: false  // Skip variable resolution (post-resolution only)
    }
  }
}

Common Use Cases

1. Injecting External Variables:

overrides: {
  default: {
    'fonts.sans': 'var(--font-inter)',      // Next.js font
    'colors.primary': 'var(--tw-primary)'   // Tailwind plugin
  }
}

2. Variant-Specific Overrides:

overrides: {
  dark: {
    'colors.background': '#000000',
    'colors.foreground': '#ffffff'
  },
  compact: {
    'radius.lg': '0',
    'spacing.base': '0.125rem'
  }
}

3. Global Overrides:

overrides: {
  '*': {
    'fonts.sans': 'Inter, -apple-system, BlinkMacSystemFont, sans-serif',
    'fonts.mono': 'JetBrains Mono, Consolas, monospace'
  }
}

4. Prototyping:

overrides: {
  default: {
    'colors.primary.500': '#ff6b6b',
    'radius.lg': '1rem'
  }
}

How It Works

Two-phase approach:

  1. Pre-resolution (Variable Injection)

    • Injects synthetic CSS variables before resolution
    • Allows overrides to participate in var() resolution
    • Applied to: 'default', 'base', '*' selectors
  2. Post-resolution (Theme Mutation)

    • Directly mutates resolved theme objects
    • Overrides final computed values
    • Applied to: all selector types

Debug Mode

tailwindResolver({
  input: 'src/styles.css',
  debug: true,
  overrides: {
    default: { 'radius.lg': '0.5rem' },
  },
});

Output:

[Overrides] Injected variable: --radius-lg = 0.5rem
[Overrides] Injected 1 variables for 'default'
[Overrides] Applied to 'default': radius.lg = 0.5rem
[Overrides] Summary for 'default': 1 applied, 0 skipped

TypeScript Configuration

Ensure the output directory is included in tsconfig.json:

{
  "include": ["src/**/*"],
  "compilerOptions": {
    "skipLibCheck": true
  }
}

If you find this helpful, follow me on X @mrstern_

About

Resolve Tailwind theme variables into JavaScript objects. Supports Tailwind v4 with future-proof versioning.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

Packages

No packages published