| name | styling-patterns |
| description | Tailwind CSS styling with CVA variants, data-slots, layout patterns, and z-index management |
Styling Patterns Skill
Quick Start
- Always start with
#ui_code_toolsscaffold for propercvaanddata-slotsetup - Use Tailwind utilities - NO inline styles unless no Tailwind equivalent exists
- Merge classes with
cn()helper (clsx + tailwind-merge)
Core Pattern: cva + cn
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
// Base styles
"inline-flex items-center justify-center rounded-md font-medium transition-colors",
{
variants: {
variant: {
primary: [
"bg-fg-brand-primary text-text-neutral-white",
"border border-transparent",
"hover:bg-fg-brand-secondary",
"disabled:border-border-neutral-disabled_subtle disabled:text-text-neutral-quinary-disabled disabled:bg-fg-neutral-disabled",
],
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 px-3 text-sm",
lg: "h-11 px-8",
}
},
defaultVariants: {
variant: "primary",
size: "default",
}
}
);
export function Button({ className, variant, size, ...props }) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
);
}
State & Variant Organization
Group related state classes together:
primary: [
"bg-fg-brand-primary text-text-neutral-white",
"border border-transparent",
"hover:bg-fg-brand-secondary",
"disabled:border disabled:border-border-neutral-disabled_subtle disabled:text-text-neutral-quinary-disabled disabled:bg-fg-neutral-disabled",
],
- Prefer variant props over boolean props
- Add new
cvavariants instead of JSX branching - Keep arrays small - extract helpers if state logic gets complex
Data Slots for Overrides
Expose data-slot attributes for targeted styling:
<div data-slot="table-wrapper">
<table data-slot="table">
<thead data-slot="thead">
{/* ... */}
</thead>
</table>
</div>
Allow consumers to override specific parts:
// Parent can now do:
<Table className="[&_*[data-slot='thead']]:bg-red-500" />
Layout Best Practices
Spacing
Use
gapover manual margins between siblings:// Good <div className="flex gap-4"> <Item /> <Item /> </div> // Avoid <div className="flex"> <Item className="mr-4" /> <Item /> </div>
Padding & Alignment
- Use consistent padding scales:
px-3,py-2.5 - Keep typography on grid with standard scales
Heights for Scroll Containers
- Use Tailwind calc with CSS variables
- Use
useMeasureVariablehook for auto-calculated heights - Avoid inline
style={{ height: ... }}
const containerRef = useMeasureVariable("container-height");
<div
ref={containerRef}
className="h-[calc(100vh-var(--container-height))] overflow-auto"
>
{/* scrollable content */}
</div>
Z-Index Scale
Respect the existing z-index scale to avoid conflicts:
// tailwind.config.js
zIndex: {
dropdown: "100",
sticky: "200",
popover: "300",
tooltip: "400",
modal: "500",
notification: "600",
}
Conditional Classes
// With cva (preferred)
const cardVariants = cva("rounded-lg", {
variants: {
elevated: {
true: "shadow-lg",
false: "border",
}
}
});
// Outside cva
className={cn([
"base-classes",
condition && "conditional-classes",
anotherCondition ? "yes-classes" : "no-classes"
])}
Key Rules
- Tailwind first - only use inline styles when necessary
- Define variants in cva - avoid branching in JSX
- Use cn() to merge - handles conflicts automatically
- Provide data-slots - for targeted overrides
- Favor flex/grid + gap - over manual spacing
- Keep class arrays readable - split long lists across lines
- Follow existing patterns - check similar components first