.config/opencode/skills/code-architecture-tailwind-v4-best-practices/SKILL.md
Guides Tailwind CSS v4 patterns for buttons and components. Use this skill when creating components with variants, choosing between CVA/tailwind-variants, or configuring Tailwind v4's CSS-first approach.
npx skillsauth add klen/dotfiles code-architecture-tailwind-v4-best-practicesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use utilities directly in markup as the primary approach. Abstract with CVA/tailwind-variants only when you have 3+ variants.
Tailwind v4's CSS-first configuration eliminates tailwind.config.js entirely.
All configuration happens in CSS via @theme directive.
@import "tailwindcss";
@theme {
--color-brand-primary: oklch(0.65 0.24 354.31);
--color-brand-secondary: oklch(0.72 0.11 178);
--font-sans: "Inter", sans-serif;
--radius-button: 0.5rem;
}
Key v4 changes:
@import "tailwindcss" replaces three @tailwind directives--color-* generates color utilities AND exposes as CSS variables.gitignore)// ✅ Simple button - no abstraction needed
<button className="
inline-flex items-center justify-center gap-2
px-4 py-2
bg-blue-500 hover:bg-blue-600 active:bg-blue-700
text-white text-sm font-medium
rounded-md transition-colors
focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500
disabled:opacity-50 disabled:pointer-events-none
">
Save Changes
</button>
import { cva, type VariantProps } from 'class-variance-authority';
const buttonVariants = cva(
// Base classes
"inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
primary: "bg-blue-500 text-white hover:bg-blue-600",
secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300",
outline: "border-2 border-blue-500 text-blue-500 hover:bg-blue-50",
ghost: "text-blue-500 hover:bg-blue-50"
},
size: {
sm: "h-8 px-3 text-xs",
md: "h-10 px-4 text-sm",
lg: "h-12 px-6 text-base"
}
},
defaultVariants: {
variant: "primary",
size: "md"
}
}
);
export type ButtonProps = VariantProps<typeof buttonVariants>;
extendimport { tv, type VariantProps } from 'tailwind-variants';
const card = tv({
slots: {
base: 'rounded-lg border bg-card shadow-sm',
header: 'flex flex-col space-y-1.5 p-6',
title: 'text-2xl font-semibold',
content: 'p-6 pt-0',
footer: 'flex items-center p-6 pt-0'
},
variants: {
variant: {
elevated: { base: 'shadow-xl' },
flat: { base: 'shadow-none border' }
}
}
});
const { base, header, title, content, footer } = card({ variant: 'elevated' });
The Tailwind team discourages @apply except in narrow circumstances.
Use component abstractions instead.
/* ❌ Avoid - hides styling decisions, breaks variant support */
.btn-primary {
@apply bg-blue-500 text-white px-4 py-2 rounded;
}
/* ✅ Use @utility for custom utilities if absolutely needed */
@utility btn-base {
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
}
| Approach | Bundle | Type Safe | Use Case | |----------|--------|-----------|----------| | Pure Tailwind | 0KB | ❌ | Simple, 1-2 variants, prototyping | | CVA | ~1KB | ✅ | Component libraries, most projects | | Tailwind-variants | ~4KB | ✅ | Complex design systems, slots |
V4 supports native data attributes for clean state management:
export function Button({ isLoading, isDisabled, children }: ButtonProps) {
return (
<button
data-loading={isLoading ?? ""}
data-disabled={isDisabled ?? ""}
className="
bg-blue-500 text-white px-4 py-2 rounded
hover:bg-blue-600
data-loading:opacity-50 data-loading:cursor-wait
data-disabled:opacity-50 data-disabled:pointer-events-none
"
>
{isLoading && <Spinner className="mr-2" />}
{children}
</button>
);
}
Custom variants via @custom-variant:
@custom-variant selected-not-disabled (&[data-selected]:not([data-disabled]));
import { tv, type VariantProps } from 'tailwind-variants';
const buttonStyles = tv({
base: "inline-flex items-center justify-center rounded-md font-medium transition-colors",
variants: {
variant: {
primary: "bg-blue-500 text-white hover:bg-blue-600",
secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300"
},
size: {
sm: "h-8 px-3 text-xs",
md: "h-10 px-4 text-sm"
}
}
});
type ButtonProps = React.ComponentProps<"button"> &
VariantProps<typeof buttonStyles>;
export function Button({ variant, size, className, ...props }: ButtonProps) {
return (
<button
data-slot="button"
className={cn(buttonStyles({ variant, size }), className)}
{...props}
/>
);
}
<button
type="button"
disabled={disabled || loading}
aria-disabled={disabled || loading}
aria-busy={loading}
aria-label={ariaLabel}
className={buttonStyles({ variant, size })}
>
{loading && <Spinner aria-hidden="true" />}
{leftIcon && <span data-slot="icon">{leftIcon}</span>}
<span data-slot="label">{children}</span>
</button>
| v3 | v4 |
|----|-----|
| shadow-sm | shadow-xs |
| rounded-sm | rounded-xs |
| bg-opacity-50 | bg-black/50 |
| bg-gradient-to-r | bg-linear-to-r |
| border (gray-200 default) | border (currentColor) |
| ring (3px blue-500) | ring-3 (currentColor) |
Automated migration: npx @tailwindcss/upgrade
@apply for component stylestools
Anti-patterns and mistakes to avoid as a product manager. Use when evaluating leadership behaviors, improving team dynamics, reflecting on management practices, or onboarding new product managers.
development
Guides proper usage of TypeScript's satisfies operator vs type annotations. Use this skill when deciding between type annotations (colon) and satisfies, validating object shapes while preserving literal types, or troubleshooting type inference issues.
development
Guides when to use interface vs type in TypeScript. Use this skill when defining object types, extending types, or choosing between interface and type aliases.
development
Guides TypeScript best practices for type safety, code organization, and maintainability. Use this skill when configuring TypeScript projects, deciding on typing strategies, writing async code, or reviewing TypeScript code quality.