<!-- Button variants — the pattern -->
<!-- Primary -->
<button class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Primary
</button>
<!-- Secondary -->
<button class="px-4 py-2 bg-white hover:bg-gray-50 text-gray-900 font-medium rounded-lg text-sm border border-gray-300 transition-colors focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2">
Secondary
</button>
<!-- Danger -->
<button class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2">
Delete
</button>
<!-- Ghost -->
<button class="px-4 py-2 hover:bg-gray-100 text-gray-700 font-medium rounded-lg text-sm transition-colors">
Ghost
</button>
<!-- Alert variants -->
<div class="flex gap-3 p-4 bg-blue-50 border border-blue-200 rounded-lg text-blue-800 text-sm">
<span>ℹ️</span>
<p>Informational message here.</p>
</div>
<div class="flex gap-3 p-4 bg-red-50 border border-red-200 rounded-lg text-red-800 text-sm">
<span>⚠️</span>
<p>Something went wrong. Please try again.</p>
</div>
<div class="flex gap-3 p-4 bg-green-50 border border-green-200 rounded-lg text-green-800 text-sm">
<span>✅</span>
<p>Successfully saved your changes.</p>
</div>
<!-- Badge component -->
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">New</span>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">Active</span>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">Error</span>
<!-- Input with label -->
<div class="flex flex-col gap-1.5">
<label class="text-sm font-medium text-gray-700" for="email">Email</label>
<input
id="email"
type="email"
class="px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder:text-gray-400"
placeholder="you@example.com"
/>
<p class="text-xs text-gray-500">We'll never share your email.</p>
</div>
<!-- Card with image -->
<div class="max-w-xs rounded-2xl overflow-hidden bg-white shadow-md hover:shadow-xl transition-shadow">
<img class="w-full h-48 object-cover" src="image.jpg" alt="Card image" />
<div class="p-5">
<span class="text-xs font-semibold text-blue-600 uppercase tracking-wider">Category</span>
<h3 class="mt-1 font-bold text-gray-900 text-lg leading-snug">Card Title Here</h3>
<p class="mt-2 text-gray-500 text-sm line-clamp-3">
Description text that might be long and gets clamped after three lines.
</p>
<div class="mt-4 flex items-center justify-between">
<span class="text-sm text-gray-400">5 min read</span>
<a href="#" class="text-sm font-medium text-blue-600 hover:text-blue-700">Read more →</a>
</div>
</div>
</div>
✦ Tip
If you're using React, Vue, or Svelte, extract repeated utility strings into components instead of `@apply`. Component abstraction is the right level — not CSS class abstraction. `@apply` is mainly useful for integrating Tailwind with plain HTML pages or third-party component slots.