Skip to main content
Technology & EngineeringTailwind319 lines

Tailwind Custom Config

Customizing tailwind.config.js to extend themes, add custom colors, fonts, spacing, and configure content paths

Quick Summary17 lines
You are an expert in configuring and extending Tailwind CSS through its configuration file.

## Key Points

- **Always use `extend` unless you need to remove defaults.** Overriding `colors` at the top level removes all built-in colors, which is rarely desired.
- **Use semantic color names** (`primary`, `surface`, `danger`) rather than visual names (`blue`, `light-gray`) so themes are swappable.
- **Keep content paths precise.** Overly broad globs like `'./**/*.{js,html}'` slow down builds and may scan `node_modules`.
- **Use `tailwind.config.ts` in TypeScript projects** for type checking and autocompletion.
- **Share configuration across projects with presets** rather than copy-pasting config files.
- **Document custom values** with comments explaining design decisions (e.g., why a specific spacing value exists).
- **Forgetting to add new file paths to `content`.** New directories or file extensions not listed in `content` result in classes being purged in production.
- **Overriding instead of extending by accident.** Placing `colors` at the `theme` level instead of `theme.extend` silently removes all default colors.
- **Stale caches after config changes.** Restart the dev server after modifying `tailwind.config.js`; some bundlers do not pick up config changes via HMR.
- **Using deprecated color names.** Colors like `lightBlue`, `warmGray`, and `trueGray` were renamed in Tailwind v3 to `sky`, `stone`, and `neutral`.
- **Overly broad content paths.** Globs like `'./**/*.{js,html}'` scan `node_modules` and slow builds dramatically. Be precise about which directories contain template files and list only those.
skilldb get tailwind-skills/Tailwind Custom ConfigFull skill: 319 lines
Paste into your CLAUDE.md or agent config

Customizing tailwind.config — Tailwind CSS

You are an expert in configuring and extending Tailwind CSS through its configuration file.

Overview

The tailwind.config.js (or tailwind.config.ts) file is the central place to customize Tailwind's default design system. You can extend or override colors, spacing, typography, breakpoints, and more. Understanding the difference between extend and top-level overrides is critical to maintaining a usable configuration.

Core Philosophy

The Tailwind configuration file is where a generic utility framework becomes your team's design system. Rather than accepting every default and sprinkling arbitrary values throughout the codebase, you invest upfront in defining your color palette, type scale, spacing rhythm, and breakpoints in one place. This configuration becomes a contract between design and engineering — every value in it is intentional, documented, and available as a utility class.

The distinction between extending and overriding is foundational to working with Tailwind's config. Extending adds your custom values alongside the defaults, giving you the full palette plus your brand colors. Overriding replaces the defaults entirely, which is powerful when you want to constrain the design system but dangerous when done accidentally. Understanding this boundary prevents the most common Tailwind configuration mistakes and keeps the utility set predictable.

Configuration should be treated as a shared artifact, not a dump of ad-hoc values. When someone adds spacing: { '13.5': '3.375rem' } without a clear design rationale, it signals a breakdown in the system. Every custom token should have a reason to exist, and presets provide the mechanism to share that reasoning across multiple projects in an organization.

Core Concepts

Configuration File Structure

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './src/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx}',
  ],
  theme: {
    // Top-level keys REPLACE the defaults entirely
    screens: {
      sm: '640px',
      md: '768px',
      lg: '1024px',
      xl: '1280px',
      '2xl': '1536px',
    },
    extend: {
      // Keys inside extend MERGE with defaults
      colors: {
        brand: {
          50: '#eff6ff',
          500: '#3b82f6',
          900: '#1e3a5f',
        },
      },
    },
  },
  plugins: [],
}

Content Paths

The content array tells Tailwind where to scan for class names so it can tree-shake unused CSS:

content: [
  './app/**/*.{js,ts,jsx,tsx}',      // Next.js app directory
  './pages/**/*.{js,ts,jsx,tsx}',     // Next.js pages
  './components/**/*.{js,ts,jsx,tsx}',
  './src/**/*.{html,js,svelte}',      // Svelte project
  './index.html',                      // Vite entry point
]

Extending vs. Overriding

theme: {
  // OVERRIDE: replaces ALL default colors with only these
  colors: {
    black: '#000',
    white: '#fff',
    primary: '#4f46e5',
  },

  extend: {
    // EXTEND: adds to default colors, keeping gray, blue, red, etc.
    colors: {
      primary: '#4f46e5',
      surface: '#f8fafc',
    },
  },
}

Always use extend unless you intentionally want to remove all defaults for that key.

Implementation Patterns

Custom Color Palette

// tailwind.config.js
const colors = require('tailwindcss/colors')

module.exports = {
  theme: {
    extend: {
      colors: {
        // Reference existing Tailwind palettes
        primary: colors.indigo,
        secondary: colors.slate,
        accent: colors.amber,

        // Custom brand colors with full shade scale
        brand: {
          50: '#faf5ff',
          100: '#f3e8ff',
          200: '#e9d5ff',
          300: '#d8b4fe',
          400: '#c084fc',
          500: '#a855f7',
          600: '#9333ea',
          700: '#7e22ce',
          800: '#6b21a8',
          900: '#581c87',
          950: '#3b0764',
        },
      },
    },
  },
}

Usage: bg-brand-500, text-brand-900, border-primary-300.

Custom Fonts

theme: {
  extend: {
    fontFamily: {
      sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
      display: ['Cal Sans', 'Inter', 'sans-serif'],
      mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
    },
  },
}

Usage: font-sans, font-display, font-mono.

Custom Spacing and Sizing

theme: {
  extend: {
    spacing: {
      '4.5': '1.125rem',  // 18px — between p-4 and p-5
      '18': '4.5rem',     // 72px
      '88': '22rem',      // 352px
    },
    maxWidth: {
      '8xl': '88rem',
      prose: '65ch',
    },
    minHeight: {
      'screen-75': '75vh',
    },
  },
}

Custom Breakpoints

theme: {
  screens: {
    xs: '475px',
    sm: '640px',
    md: '768px',
    lg: '1024px',
    xl: '1280px',
    '2xl': '1536px',
    '3xl': '1920px',
  },
}

Note: screens is typically overridden (not extended) so you control the full set and ordering.

Custom Animations

theme: {
  extend: {
    keyframes: {
      'fade-in': {
        '0%': { opacity: '0', transform: 'translateY(10px)' },
        '100%': { opacity: '1', transform: 'translateY(0)' },
      },
      'slide-in-right': {
        '0%': { transform: 'translateX(100%)' },
        '100%': { transform: 'translateX(0)' },
      },
    },
    animation: {
      'fade-in': 'fade-in 0.3s ease-out',
      'slide-in-right': 'slide-in-right 0.3s ease-out',
    },
  },
}

Using CSS Variables for Theming

theme: {
  extend: {
    colors: {
      background: 'hsl(var(--background))',
      foreground: 'hsl(var(--foreground))',
      card: 'hsl(var(--card))',
      'card-foreground': 'hsl(var(--card-foreground))',
      primary: 'hsl(var(--primary))',
      'primary-foreground': 'hsl(var(--primary-foreground))',
    },
    borderRadius: {
      lg: 'var(--radius)',
      md: 'calc(var(--radius) - 2px)',
      sm: 'calc(var(--radius) - 4px)',
    },
  },
}

This pattern (used by shadcn/ui) allows switching entire themes by changing CSS variable values.

TypeScript Configuration

// tailwind.config.ts
import type { Config } from 'tailwindcss'

const config: Config = {
  content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
  theme: {
    extend: {
      colors: {
        brand: {
          500: '#6366f1',
        },
      },
    },
  },
  plugins: [],
}

export default config

Presets for Shared Configuration

// tailwind-preset.js — shared across multiple projects
module.exports = {
  theme: {
    extend: {
      colors: {
        brand: { /* ... */ },
      },
      fontFamily: {
        sans: ['Inter', 'sans-serif'],
      },
    },
  },
  plugins: [
    require('@tailwindcss/typography'),
  ],
}

// tailwind.config.js — per-project
module.exports = {
  presets: [require('./tailwind-preset')],
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {
      // Project-specific overrides merge on top of preset
    },
  },
}

Best Practices

  • Always use extend unless you need to remove defaults. Overriding colors at the top level removes all built-in colors, which is rarely desired.
  • Use semantic color names (primary, surface, danger) rather than visual names (blue, light-gray) so themes are swappable.
  • Keep content paths precise. Overly broad globs like './**/*.{js,html}' slow down builds and may scan node_modules.
  • Use tailwind.config.ts in TypeScript projects for type checking and autocompletion.
  • Share configuration across projects with presets rather than copy-pasting config files.
  • Document custom values with comments explaining design decisions (e.g., why a specific spacing value exists).

Common Pitfalls

  • Forgetting to add new file paths to content. New directories or file extensions not listed in content result in classes being purged in production.
  • Overriding instead of extending by accident. Placing colors at the theme level instead of theme.extend silently removes all default colors.
  • Stale caches after config changes. Restart the dev server after modifying tailwind.config.js; some bundlers do not pick up config changes via HMR.
  • Conflicting preset and local config. When a preset defines a key and the local config also defines it under extend, they merge. If both define it at the top level, the local config wins entirely.
  • Using deprecated color names. Colors like lightBlue, warmGray, and trueGray were renamed in Tailwind v3 to sky, stone, and neutral.

Anti-Patterns

  • The junk-drawer config. Adding arbitrary spacing values, one-off colors, and custom breakpoints without design justification turns the config into a grab bag. Every custom token should trace back to a design decision, not a developer's impulse to pixel-match a mockup.

  • Overriding when you meant to extend. Placing colors at the theme top level instead of inside theme.extend silently removes every default color. This is the single most common config mistake and results in bg-gray-100 suddenly producing no output.

  • Duplicating config across projects. Copy-pasting tailwind.config.js between repositories leads to drift. Use Tailwind presets to share a canonical configuration and let individual projects extend it with project-specific additions.

  • Overly broad content paths. Globs like './**/*.{js,html}' scan node_modules and slow builds dramatically. Be precise about which directories contain template files and list only those.

  • Ignoring the config after initial setup. The configuration should evolve with the design system. When designers introduce a new spacing value or deprecate a color, the config should be updated and the old tokens removed, not left to accumulate indefinitely.

Install this skill directly: skilldb add tailwind-skills

Get CLI access →