Tailwind CSS Prefixing for Modules

How LaraDashboard modules use Tailwind CSS v4 prefixing to isolate styles and prevent class conflicts between modules.

Tailwind CSS Prefixing for Modules

Every LaraDashboard module that ships its own CSS uses Tailwind CSS v4 prefixing to namespace utility classes. This prevents style conflicts between modules that run on the same page.

Why Prefixing?

When multiple modules are active, each generates its own CSS bundle. Without prefixing, a .bg-white from Module A could conflict with .bg-white from Module B or the core app. Prefixing ensures each module's utilities are scoped.

How It Works

1. Declare the Prefix

In your module's resources/assets/css/app.css:

@import "tailwindcss" prefix(crm);

This generates all Tailwind utilities with the crm: prefix: crm:bg-white, crm:flex, crm:py-4, etc.

2. Use Prefixed Classes in Blade

{{-- Module-specific classes use the prefix --}}
<div class="crm:py-6 crm:px-4 crm:bg-white crm:dark:bg-gray-900 crm:rounded-lg">
    <h2 class="crm:text-xl crm:font-semibold crm:text-gray-900 crm:dark:text-white">
        Contacts
    </h2>
    <p class="crm:mt-2 crm:text-sm crm:text-gray-600 crm:dark:text-gray-400">
        Manage your contacts here.
    </p>
</div>

3. Shared Classes Do NOT Use Prefix

Classes from the core app's resources/css/components.css are globally available without any prefix:

{{-- Core component classes — no prefix needed --}}
<button class="btn btn-primary">Save Contact</button>
<input type="text" class="form-control" placeholder="Search...">
<label class="form-label">Name</label>
<span class="badge badge-success">Active</span>
<div class="card">
    <div class="card-header">
        <h3 class="card-title">Card Title</h3>
    </div>
    <div class="card-body">Content</div>
</div>

4. Mix Shared + Prefixed

In practice, views combine both:

<div class="card">
    <div class="card-header crm:flex crm:items-center crm:justify-between">
        <h3 class="card-title">Contacts</h3>
        <a href="{{ route('admin.crm.contacts.create') }}" class="btn btn-primary btn-sm">
            Add Contact
        </a>
    </div>
    <div class="card-body crm:p-0">
        {{-- Datatable component --}}
    </div>
</div>

Existing Module Prefixes

Module Prefix Example Usage
CRM crm crm:py-4 crm:flex crm:gap-2
Starter26 (theme) st st:py-4 st:flex st:gap-2
DocForge df df:py-4 df:flex
Ecom ecom ecom:py-4 ecom:flex
Forum forum forum:py-4 forum:flex
Review review review:py-4 review:flex
Custom Forms cf cf:py-4 cf:flex
LaraDashboard Core ld ld:py-4 ld:flex
LaradashboardPro laradashboardpro laradashboardpro:py-4

Tip: Choose a short prefix (2-5 characters) for readability. Shorter is better.

Full CSS Setup

resources/assets/css/app.css

@import "tailwindcss" prefix(crm);

/* Class-based dark mode (matches core app) */
@custom-variant dark (&:is(.dark *));

/* Tell Tailwind where to scan for class usage */
@source '../../views/**/*.blade.php';
@source '../js/**/*.js';

/* Define theme colors */
@theme {
    --color-primary: #635bff;
}

/* Safelist classes used dynamically (Alpine.js, Livewire, etc.) */
@source inline("border-primary border-2 border-gray-200 border-gray-300");
@source inline("bg-primary bg-primary/5 bg-primary/20 bg-white");
@source inline("text-primary text-primary/80");

/* Inherit theme color from core app at runtime */
@layer base {
    :root, :host {
        --crm-color-primary: var(--color-primary, #635bff);
    }
}

/* Module-specific component styles (optional) */
@layer components {
    /* Custom styles here */
}

Key Sections Explained

Section Purpose
prefix(crm) Namespaces all Tailwind utilities
@custom-variant dark Matches core app's class-based dark mode
@source Tells Tailwind where to scan for used classes
@theme Defines module's color tokens
@source inline(...) Safelists classes set dynamically via JS/Alpine
@layer base Inherits primary color from core app theme

Dark Mode

Dark mode uses the prefix too:

<div class="crm:bg-white crm:dark:bg-gray-900">
    <p class="crm:text-gray-900 crm:dark:text-white">
        Adapts to dark mode
    </p>
</div>

The @custom-variant dark (&:is(.dark *)) directive ensures dark mode activates when the <html> element has the .dark class, matching the core app's behavior.

Dynamic Classes & Safelisting

Tailwind v4 only generates CSS for classes it finds in your source files. Classes added dynamically (e.g., by Alpine.js or Livewire) must be safelisted:

/* Safelist classes that are computed at runtime */
@source inline("crm:bg-green-100 crm:bg-red-100 crm:bg-yellow-100");
@source inline("crm:text-green-800 crm:text-red-800 crm:text-yellow-800");

Common scenarios requiring safelists:

  • Status badges with dynamic colors
  • Alpine x-bind:class with computed values
  • Livewire conditional class rendering

Vite Configuration

Each module needs its own vite.config.js:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import tailwindcss from '@tailwindcss/vite';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const isDistBuild = process.env.MODULE_DIST_BUILD === 'true';

export default defineConfig({
    build: {
        outDir: isDistBuild
            ? path.resolve(__dirname, 'dist/build-crm')
            : path.resolve(__dirname, '../../public/build-crm'),
        emptyOutDir: true,
        manifest: 'manifest.json',
    },
    plugins: [
        laravel({
            publicDirectory: isDistBuild
                ? path.resolve(__dirname, 'dist')
                : path.resolve(__dirname, '../../public'),
            buildDirectory: 'build-crm',
            input: [
                path.resolve(__dirname, 'resources/assets/css/app.css'),
                path.resolve(__dirname, 'resources/assets/js/app.js'),
            ],
            refresh: true,
        }),
        tailwindcss(),
    ],
});

Build Output

Mode Output Directory When
Development public/build-{module}/ Local dev, hot reload
Distribution modules/{Module}/dist/build-{module}/ ZIP packaging for marketplace

Loading in Blade

@vite([
    'resources/assets/css/app.css',
    'resources/assets/js/app.js',
], 'build-crm')

Building Module Assets

# From project root (compiles all modules)
npm run dev

# Module-specific compilation via artisan
php artisan module:compile-css CRM --watch     # Development with hot reload
php artisan module:compile-css CRM --minify    # Production build
php artisan module:compile-css CRM --minify --dist  # Distribution build

# From module directory
cd modules/Crm && npm install && npm run build

Common Mistakes

1. Forgetting the Prefix

{{-- WRONG: Classes won't be generated --}}
<div class="py-4 flex gap-2">

{{-- CORRECT: Use module prefix --}}
<div class="crm:py-4 crm:flex crm:gap-2">

2. Prefixing Shared Classes

{{-- WRONG: Shared classes don't have prefix --}}
<button class="crm:btn crm:btn-primary">

{{-- CORRECT: Shared classes are global --}}
<button class="btn btn-primary">

3. Missing Dark Mode Prefix

{{-- WRONG: dark variant needs prefix too --}}
<div class="crm:bg-white dark:bg-gray-900">

{{-- CORRECT: Prefix on both --}}
<div class="crm:bg-white crm:dark:bg-gray-900">

4. Dynamic Classes Not Safelisted

{{-- Alpine sets class dynamically — won't work unless safelisted --}}
<div :class="isActive ? 'crm:bg-green-100' : 'crm:bg-gray-100'">

Add to CSS: @source inline("crm:bg-green-100 crm:bg-gray-100");

Shared Classes Reference

These core classes are available in ALL modules without prefix:

Category Classes
Buttons btn, btn-primary, btn-secondary, btn-danger, btn-success, btn-warning, btn-info, btn-default, btn-link, btn-outline-*, btn-sm, btn-lg, btn-icon, btn-icon-primary
Forms form-control, form-label, form-control-textarea, form-control-file, form-control-combobox, form-checkbox, form-input-group
Cards card, card-header, card-body, card-footer, card-title, card-description
Badges badge, badge-primary, badge-success, badge-danger, badge-warning, badge-info, badge-secondary
Tables table, table-responsive, table-thead, table-thead-th, table-tr, table-td
Alerts alert, alert-success, alert-danger, alert-warning, alert-info
Typography page-title, page-description, section-title, prose
Links link, link-secondary, link-danger
Layout ld-container

Next Steps

/