Customization

Extending the theme in tailwind.config.js, using @apply for repeated utility patterns, and writing a small plugin.

Extending the theme

tailwind.config.js is where a project's design tokens live — brand colors, custom fonts, extra spacing values. The theme.extend key adds to Tailwind's defaults without replacing them, which is almost always what you want (replacing theme.colors directly, instead of extending it, would delete every built-in color).

Javascript
// tailwind.config.js
module.exports = {
    content: ['./resources/**/*.{blade.php,js}'],
    theme: {
        extend: {
            colors: {
                brand: {
                    50:  '#eff6ff',
                    500: '#3b82f6',
                    600: '#2563eb',
                    900: '#1e3a8a',
                },
            },
            fontFamily: {
                sans: ['Inter', 'system-ui', 'sans-serif'],
                display: ['"Cal Sans"', 'sans-serif'],
            },
            spacing: {
                18: '4.5rem',
                '128': '32rem',
            },
        },
    },
    plugins: [],
};

Once declared, these become ordinary utility classes, indistinguishable from Tailwind's built-in ones:

HTML
<button class="bg-brand-600 hover:bg-brand-900 font-display px-18">
    Get started
</button>
  • colors.brand generates bg-brand-500, text-brand-600, border-brand-900, etc. — every color utility variant, automatically, for the new palette.
  • fontFamily.sans overrides what the default font-sans utility (and Tailwind's @tailwind base typography reset) actually renders.
  • spacing.18 becomes usable anywhere a spacing utility is expected — p-18, w-18, gap-18 all work once it's declared once.

@apply — extracting repeated utility combinations

When the exact same long utility string is repeated across many elements, @apply lets you fold that combination into a single custom class, inside your CSS file:

Css
/* src/styles.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
    .btn-primary {
        @apply bg-blue-600 hover:bg-blue-700 text-white font-semibold px-4 py-2 rounded-lg transition;
    }

    .card {
        @apply bg-white rounded-xl shadow-md p-6 border border-gray-100;
    }
}
HTML
<button class="btn-primary">Save</button>
<div class="card">...</div>

This trades markup verbosity for a small amount of custom CSS again — worth it for a component repeated dozens of times across a codebase (like a primary button), but easy to overuse. Reaching for @apply on every element defeats much of the point of utility-first CSS: styling stops being visible directly in the markup, and you're back to needing to open a separate CSS file to understand what a class does.

Writing a small plugin

Tailwind plugins let you register entirely new utilities (or components) programmatically, which is useful for patterns theme.extend can't express — like a utility with a fixed, non-scale value, or one derived from a computed formula.

Javascript
// tailwind.config.js
const plugin = require('tailwindcss/plugin');

module.exports = {
    content: ['./resources/**/*.{blade.php,js}'],
    theme: { extend: {} },
    plugins: [
        plugin(function ({ addUtilities }) {
            addUtilities({
                '.text-shadow': {
                    'text-shadow': '0 2px 4px rgba(0, 0, 0, 0.2)',
                },
                '.scrollbar-hide': {
                    '-ms-overflow-style': 'none',
                    'scrollbar-width': 'none',
                    '&::-webkit-scrollbar': { display: 'none' },
                },
            });
        }),
    ],
};
HTML
<h1 class="text-shadow">Title with a soft drop shadow</h1>
<div class="overflow-x-auto scrollbar-hide">...</div>

Plugins are also how popular official add-ons like @tailwindcss/forms and @tailwindcss/typography work under the hood — they're just registered in the plugins array after installing them as npm packages.

Common mistakes

  • Overwriting theme.colors (or theme.spacing, etc.) directly instead of using theme.extend.colors — this replaces Tailwind's entire default palette rather than adding to it, silently breaking every built-in color class used elsewhere in the project.
  • Reaching for @apply as the default way to write Tailwind, rather than the exception for genuinely repeated component patterns — used everywhere, it erodes the main benefit of seeing styles directly in markup.
  • Forgetting to restart the dev server (or rebuild) after editing tailwind.config.js — config changes aren't always picked up by an already-running watch process depending on the build tool.