View : 206

12/05/2026 01:14am

EP 7: Stop Writing Repetitive Code! Create Components with @apply and Custom CSS

EP 7: Stop Writing Repetitive Code! Create Components with @apply and Custom CSS

#tailwind css components

#component system development

#Tailwind CSS

#tailwind css @apply directive

Many developers who start using Tailwind CSS encounter the same problem: HTML that looks cluttered and verbose, and having to write the same classes over and over again. For example, creating a blue button requires typing px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors every single time.

But did you know that Tailwind CSS has powerful tools to solve this problem? Today we'll learn how to use @apply and component creation techniques that will make you code faster, maintain easier, while keeping the flexibility of Tailwind CSS intact.

Understanding the @apply Directive

@apply is a special tool in Tailwind CSS that allows us to combine multiple utility classes into a single class, enabling us to create reusable components.

Basic Principles of @apply

/* Create basic buttons with @apply */
.btn {
  @apply px-6 py-3 font-semibold rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2;
}

.btn-primary {
  @apply bg-blue-500 text-white hover:bg-blue-600 focus:ring-blue-500;
}

.btn-secondary {
  @apply bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500;
}

.btn-danger {
  @apply bg-red-500 text-white hover:bg-red-600 focus:ring-red-500;
}
<!-- Usage in HTML -->
<button class="btn btn-primary">Primary Button</button>
<button class="btn btn-secondary">Secondary Button</button>
<button class="btn btn-danger">Delete Button</button>

Benefits of Using @apply

  • Cleaner HTML: Reduces the length of class attributes
  • Easy Maintenance: Edit once, apply everywhere
  • Design System Creation: Clear standards and consistency
  • Maintained Flexibility: Can still add utility classes as needed

When to Use @apply

/* ✅ Good - Frequently used patterns */
.card {
  @apply bg-white rounded-lg shadow-md p-6 border border-gray-200;
}

.input-field {
  @apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500;
}

/* ❌ Bad - Used only once */
.unique-element {
  @apply text-red-500 font-bold;
}

Building a Professional Component System

Button Components

/* Base Button */
.btn {
  @apply inline-flex items-center justify-center px-4 py-2 font-medium rounded-md transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed;
}

/* Button Sizes */
.btn-sm {
  @apply px-3 py-1.5 text-sm;
}

.btn-md {
  @apply px-4 py-2 text-base;
}

.btn-lg {
  @apply px-6 py-3 text-lg;
}

/* Button Variants */
.btn-solid {
  @apply shadow-sm;
}

.btn-outline {
  @apply border-2 bg-transparent;
}

/* Button Colors */
.btn-blue {
  @apply bg-blue-500 text-white hover:bg-blue-600 focus:ring-blue-500;
}

.btn-blue.btn-outline {
  @apply border-blue-500 text-blue-500 hover:bg-blue-500 hover:text-white;
}

.btn-green {
  @apply bg-green-500 text-white hover:bg-green-600 focus:ring-green-500;
}

.btn-green.btn-outline {
  @apply border-green-500 text-green-500 hover:bg-green-500 hover:text-white;
}
<!-- Button System Usage -->
<div class="space-y-4">
  <!-- Different sizes -->
  <div class="space-x-2">
    <button class="btn btn-sm btn-solid btn-blue">Small</button>
    <button class="btn btn-md btn-solid btn-blue">Medium</button>
    <button class="btn btn-lg btn-solid btn-blue">Large</button>
  </div>

  <!-- Different variants -->
  <div class="space-x-2">
    <button class="btn btn-md btn-solid btn-blue">Solid</button>
    <button class="btn btn-md btn-outline btn-blue">Outline</button>
  </div>

  <!-- Different colors -->
  <div class="space-x-2">
    <button class="btn btn-md btn-solid btn-blue">Blue</button>
    <button class="btn btn-md btn-solid btn-green">Green</button>
  </div>
</div>

Card Components

/* Base Card */
.card {
  @apply bg-white rounded-lg shadow-md overflow-hidden;
}

.card-hover {
  @apply transition-all duration-300 hover:shadow-lg hover:-translate-y-1;
}

.card-bordered {
  @apply border border-gray-200;
}

/* Card Parts */
.card-header {
  @apply px-6 py-4 border-b border-gray-200 bg-gray-50;
}

.card-body {
  @apply px-6 py-4;
}

.card-footer {
  @apply px-6 py-4 border-t border-gray-200 bg-gray-50;
}

.card-image {
  @apply w-full h-48 object-cover;
}

.card-title {
  @apply text-xl font-semibold text-gray-800 mb-2;
}

.card-text {
  @apply text-gray-600 leading-relaxed;
}
<!-- Card System Usage -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
  <!-- Card with Header -->
  <div class="card card-hover card-bordered">
    <div class="card-header">
      <h3 class="card-title">Article Title</h3>
    </div>
    <div class="card-body">
      <p class="card-text">
        Interesting and useful article content for readers
      </p>
    </div>
    <div class="card-footer">
      <button class="btn btn-sm btn-solid btn-blue">Read More</button>
    </div>
  </div>

  <!-- Card with Image -->
  <div class="card card-hover">
    <img class="card-image" src="https://images.unsplash.com/photo-1518837695005-2083093ee35b?w=400" alt="Card image" loading="lazy">
    <div class="card-body">
      <h3 class="card-title">Product Name</h3>
      <p class="card-text">Interesting product details</p>
      <div class="mt-4 flex items-center justify-between">
        <span class="text-2xl font-bold text-blue-600">$199</span>
        <button class="btn btn-sm btn-solid btn-green">Buy Now</button>
      </div>
    </div>
  </div>
</div>

Form Components

/* Form Elements */
.form-group {
  @apply mb-4;
}

.form-label {
  @apply block text-sm font-medium text-gray-700 mb-2;
}

.form-input {
  @apply w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors;
}

.form-input.error {
  @apply border-red-300 focus:ring-red-500 focus:border-red-500;
}

.form-textarea {
  @apply form-input resize-vertical min-h-[100px];
}

.form-select {
  @apply form-input appearance-none bg-no-repeat bg-right pr-10;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
}

.form-error {
  @apply text-sm text-red-600 mt-1;
}

.form-help {
  @apply text-sm text-gray-500 mt-1;
}
<!-- Form System Usage -->
<form class="max-w-lg mx-auto space-y-6">
  <div class="form-group">
    <label class="form-label" for="name">Full Name</label>
    <input class="form-input" type="text" id="name" placeholder="Enter your name">
    <p class="form-help">Please enter your real name</p>
  </div>

  <div class="form-group">
    <label class="form-label" for="email">Email</label>
    <input class="form-input error" type="email" id="email" placeholder="your@email.com">
    <p class="form-error">Please enter a valid email address</p>
  </div>

  <div class="form-group">
    <label class="form-label" for="country">Country</label>
    <select class="form-select" id="country">
      <option>Select Country</option>
      <option>Thailand</option>
      <option>Singapore</option>
      <option>Malaysia</option>
    </select>
  </div>

  <div class="form-group">
    <label class="form-label" for="message">Message</label>
    <textarea class="form-textarea" id="message" placeholder="Write your message..."></textarea>
  </div>

  <button class="btn btn-lg btn-solid btn-blue w-full">Submit</button>
</form>

Managing Custom Utilities and Components

Using @layer Correctly

@layer base {
  /* Reset and Base Styles */
  html {
    @apply scroll-smooth;
  }
  
  body {
    @apply font-sans text-gray-900 leading-relaxed;
  }
}

@layer components {
  /* Component Classes */
  .btn {
    @apply px-4 py-2 font-medium rounded-lg transition-all duration-200;
  }
  
  .card {
    @apply bg-white rounded-lg shadow-md overflow-hidden;
  }
  
  .glass-effect {
    @apply backdrop-blur-sm bg-white/80 border border-white/20;
  }
}

@layer utilities {
  /* Custom Utilities */
  .scroll-snap-x {
    scroll-snap-type: x mandatory;
  }
  
  .scroll-snap-start {
    scroll-snap-align: start;
  }
  
  .text-shadow {
    text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
}

Creating Responsive Components

/* Responsive Card */
.card-responsive {
  @apply bg-white rounded-lg shadow-md overflow-hidden;
  @apply flex flex-col md:flex-row;
}

.card-responsive .card-image {
  @apply w-full md:w-1/3 h-48 md:h-auto object-cover;
}

.card-responsive .card-content {
  @apply p-6 flex-1;
}

.card-responsive .card-title {
  @apply text-xl font-semibold mb-2 md:text-2xl;
}

.card-responsive .card-text {
  @apply text-gray-600 text-sm md:text-base;
}

Best Practices for Component Development

1. Component Naming Conventions

/* ✅ Good - Meaningful names */
.btn-primary { }
.card-product { }
.form-input-error { }
.navbar-mobile { }

/* ❌ Bad - Vague names */
.blue-button { }
.box1 { }
.input2 { }
.nav { }

2. Organizing Structure

/* Organized by importance */

/* 1. Base Components */
.btn { }
.card { }
.form-input { }

/* 2. Component Variants */
.btn-primary { }
.btn-secondary { }
.card-hover { }
.card-bordered { }

/* 3. Component Modifiers */
.btn-sm { }
.btn-lg { }

/* 4. State Classes */
.btn:disabled { }
.form-input.error { }

3. Performance Tips

/* ✅ Good - Use @apply only when necessary */
.frequently-used-button {
  @apply px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors;
}

/* ❌ Bad - Using @apply for everything */
.one-time-use {
  @apply text-red-500 font-bold; /* Should use utility classes directly */
}

/* ✅ Good - Combine with utility classes */
.special-card {
  @apply card;
  /* Add utility classes as needed */
}

Common Pitfalls and Solutions

1. Using @apply with Pseudo-classes Incorrectly

/* ❌ Doesn't work */
.btn {
  @apply px-4 py-2 bg-blue-500:hover;
}

/* ✅ Correct */
.btn {
  @apply px-4 py-2 bg-blue-500 hover:bg-blue-600;
}

/* ✅ Or separate them */
.btn {
  @apply px-4 py-2 bg-blue-500;
}

.btn:hover {
  @apply bg-blue-600;
}

2. Misusing !important

/* ❌ Bad */
.btn {
  @apply px-4 py-2 !bg-blue-500;
}

/* ✅ Good - Use specificity */
.btn.btn-important {
  @apply bg-blue-500;
}

3. Bundle Size Considerations

/* ⚠️ Warning - May increase bundle size */
.every-single-element {
  @apply p-4 m-2 bg-white rounded shadow;
}

/* ✅ Good - Use only when necessary */
.common-card {
  @apply p-4 m-2 bg-white rounded shadow;
}

Working with JavaScript Frameworks

React Integration

// components/Button.jsx
import clsx from 'clsx';

const Button = ({ 
  variant = 'primary',
  size = 'md', 
  children, 
  className,
  ...props 
}) => {
  return (
    <button 
      className={clsx(
        'btn',
        `btn-${variant}`,
        `btn-${size}`,
        className
      )}
      {...props}
    >
      {children}
    </button>
  );
};

// Usage
<Button variant="primary" size="lg" className="shadow-lg">
  Click Here
</Button>

Vue.js Integration

<!-- components/Card.vue -->
<template>
  <div :class="cardClasses">
    <img v-if="image" class="card-image" :src="image" :alt="title" loading="lazy">
    <div class="card-body">
      <h3 v-if="title" class="card-title">{{ title }}</h3>
      <p v-if="description" class="card-text">{{ description }}</p>
      <slot></slot>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    variant: {
      type: String,
      default: 'default'
    },
    hover: {
      type: Boolean,
      default: false
    },
    image: String,
    title: String,
    description: String
  },
  computed: {
    cardClasses() {
      return [
        'card',
        this.hover && 'card-hover',
        `card-${this.variant}`
      ].filter(Boolean);
    }
  }
}
</script>

Configuration and Development Workflow

Tailwind Config for Component System

// tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{html,js,jsx,ts,tsx,vue}',
    './components/**/*.{html,js,jsx,ts,tsx,vue}'
  ],
  theme: {
    extend: {
      colors: {
        primary: {
          50: '#eff6ff',
          500: '#3b82f6',
          600: '#2563eb',
          700: '#1d4ed8',
        }
      },
      spacing: {
        '18': '4.5rem',
      }
    }
  },
  plugins: [
    require('@tailwindcss/forms'),
  ]
}

Build Script Setup

{
  "scripts": {
    "dev": "tailwindcss -i ./src/input.css -o ./dist/output.css --watch",
    "build": "tailwindcss -i ./src/input.css -o ./dist/output.css --minify"
  }
}

Documenting Components

/**
 * Button Component
 * 
 * Usage:
 * <button class="btn btn-primary btn-lg">Click me</button>
 * 
 * Variants: primary, secondary, green
 * Sizes: sm, md, lg
 * States: disabled
 */
.btn {
  @apply inline-flex items-center justify-center px-4 py-2 font-medium rounded-lg;
  @apply transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2;
  @apply disabled:opacity-50 disabled:cursor-not-allowed;
}

/**
 * Card Component
 * 
 * Usage:
 * <div class="card card-hover">
 *   <div class="card-body">
 *     <h3 class="card-title">Title</h3>
 *     <p class="card-text">Content</p>
 *   </div>
 * </div>
 * 
 * Modifiers: hover, bordered
 */
.card {
  @apply bg-white rounded-lg shadow-md overflow-hidden;
}

Summary

Creating a Component System with @apply and Custom CSS in Tailwind CSS is a powerful technique that allows us to:

Key Benefits:

  • Code Faster: Use ready-to-use components
  • Cleaner HTML: Reduce class attribute length
  • Easy Maintenance: Edit once, apply everywhere
  • Build Design Systems: Clear standards and consistency
  • Maintain Flexibility: Still able to add utility classes

Important Principles:

  • Use @apply only for frequently used patterns
  • Name components meaningfully
  • Organize with correct @layer usage
  • Consider Performance and Bundle Size
  • Document components thoroughly

Things to Watch Out For:

  • Don't use @apply for everything
  • Be mindful of Bundle Size when creating too many components
  • Test Browser Compatibility for new CSS properties
  • Maintain correct CSS import order

When to Use Component System:

  • Large Projects: When you have many repeated patterns
  • Team Development: When multiple developers need consistency
  • Design Systems: When building a comprehensive UI library
  • Maintenance: When you need to update styles across many elements

In the next episode (EP 8), we'll learn about advanced Customization, how to customize Tailwind to make it your own, create Custom Themes, and use various Plugins that will make your Tailwind CSS special and perfectly suited to your projects!

Ready to stop writing repetitive code? Follow Superdev School to learn professional web development techniques and don't forget to try creating your own Component System!

Read more

🔵 Facebook: Superdev School  (Superdev)

📸 Instagram: superdevschool

🎬 TikTok: superdevschool

🌐 Website: www.superdev.school