CSS conflicts in Lovable happen when Tailwind utility classes clash with shadcn/ui component defaults or when custom styles have lower specificity than the design system. Use the cn() utility function from shadcn/ui to merge class names correctly — it handles Tailwind class conflicts by letting the last class win. Avoid using !important. When customizing shadcn/ui components, pass className props rather than overriding global CSS, and use the Design view → Themes tab for consistent color and spacing changes.
Why CSS conflicts arise between components in Lovable projects
Lovable projects use Tailwind CSS for utility-based styling and shadcn/ui (built on Radix UI) for pre-built components. These two systems work well together, but conflicts occur when Tailwind utility classes compete with the default styles baked into shadcn/ui components. The root cause is CSS specificity. When you add className="bg-blue-500" to a shadcn/ui Button, the button may still show its default background color because the component's internal styles have equal or higher specificity. Tailwind classes and component defaults are both single-class selectors, so the one defined later in the stylesheet wins. Since shadcn/ui styles are imported after Tailwind's base layer, they often override your custom utilities. The cn() utility function (included in every Lovable project at src/lib/utils.ts) solves this by intelligently merging Tailwind classes. It uses the tailwind-merge library under the hood, which knows that bg-blue-500 and bg-primary are conflicting classes and keeps only the last one. Without cn(), both classes exist in the DOM and the browser picks the winner based on stylesheet order, which is unpredictable.
- Tailwind utility class overridden by shadcn/ui component default styles due to stylesheet order
- Not using cn() to merge custom className with component defaults — both conflicting classes stay in the DOM
- Global CSS in index.css or globals.css overriding component-level Tailwind classes
- Tailwind's dark mode classes conflicting with the shadcn/ui theme system
- Using !important to force styles, which then becomes impossible to override later
Error messages you might see
Custom className not applying to shadcn/ui componentThe shadcn/ui component has a default class that conflicts with your custom class. Use cn() to merge them: cn('default-class', customClassName). The cn() function handles conflicts by keeping the last class.
Tailwind class bg-red-500 not working on Button componentThe Button component has a default background defined in its variants. Override it by passing the variant='ghost' or variant='outline' prop to remove the default background, then apply your custom color class.
Styles look different in preview vs published siteCSS class order can differ between development and production builds. Use cn() consistently for all dynamic class merging to ensure the same class wins in both environments.
Before you start
- A Lovable project using the default Tailwind CSS + shadcn/ui stack
- Understanding that Lovable components are in src/components/ui/ and can be customized
- The cn() utility function available at src/lib/utils.ts (included by default in all Lovable projects)
How to fix it
Use cn() to merge custom classes with component defaults
cn() uses tailwind-merge to resolve conflicting Tailwind classes, keeping only the last one specified
Use cn() to merge custom classes with component defaults
cn() uses tailwind-merge to resolve conflicting Tailwind classes, keeping only the last one specified
When you need to customize a shadcn/ui component's styling, use the cn() function to merge your custom className with the component's defaults. The cn() function is already available in every Lovable project at src/lib/utils.ts. Pass the default classes first and your custom classes last — cn() ensures your custom classes win when there is a conflict. Import cn from @/lib/utils in any component file.
// Custom bg class ignored because component default winsimport { Button } from "@/components/ui/button";<Button className="bg-red-500 hover:bg-red-600"> Delete</Button>{/* Button still shows the default primary background */}// Using cn() inside the Button component definition to support overrides// In src/components/ui/button.tsx:import { cn } from "@/lib/utils";const Button = ({ className, variant, ...props }) => { return ( <button className={cn( buttonVariants({ variant }), // Default styles first className // Custom styles last — cn() resolves conflicts )} {...props} /> );};// Usage — now bg-red-500 correctly overrides the default:<Button className="bg-red-500 hover:bg-red-600">Delete</Button>Expected result: The Button renders with a red background because cn() resolves the conflict between bg-primary (default) and bg-red-500 (custom) in favor of the last class.
Use component variants instead of overriding styles directly
shadcn/ui components have built-in variants designed to be customized without CSS conflicts
Use component variants instead of overriding styles directly
shadcn/ui components have built-in variants designed to be customized without CSS conflicts
Before adding custom Tailwind classes, check if the shadcn/ui component has a variant that matches what you need. Button has variants like default, destructive, outline, secondary, ghost, and link. Using a variant gives you consistent styling that is already tested with the theme system. Only add custom classes when no variant matches your design.
// Fighting the default variant with custom classes<Button className="bg-transparent border border-gray-300 text-gray-700 hover:bg-gray-50"> Cancel</Button>{/* Many conflicting classes needed to override the default variant */}// Using the outline variant — achieves the same look with one prop<Button variant="outline"> Cancel</Button>{/* Clean, themeable, no class conflicts */}Expected result: The button renders with an outline style that matches the app's theme. No custom CSS overrides needed.
Use Design view → Themes tab for global style changes
Changing theme values in one place updates all components consistently, avoiding per-component CSS overrides
Use Design view → Themes tab for global style changes
Changing theme values in one place updates all components consistently, avoiding per-component CSS overrides
For global style changes (primary color, border radius, font family, spacing), use Lovable's Design view instead of overriding individual components. Click the + button next to Preview, open Design view, and go to the Themes tab. Change the primary color, and every shadcn/ui component that uses the primary color updates automatically. This avoids the CSS conflicts that come from manually overriding individual components with different colors.
/* Manually overriding every component's color */.custom-button { @apply bg-blue-600; }.custom-card { @apply border-blue-600; }.custom-link { @apply text-blue-600; }/* Inconsistent if you miss one component *//* Using CSS variables set by the Themes tab *//* In tailwind.config.ts, primary maps to a CSS variable: *//* --primary: 220 90% 56%; (set via Design view → Themes) *//* All components use the same primary automatically: */<Button>Uses primary</Button><Card className="border-primary">Uses primary</Card><Link className="text-primary">Uses primary</Link>Expected result: All components share the same primary color set in the theme. Changing the theme value in Design view updates everything at once.
Avoid !important and diagnose specificity issues properly
Using !important creates a specificity arms race that makes future style changes nearly impossible
Avoid !important and diagnose specificity issues properly
Using !important creates a specificity arms race that makes future style changes nearly impossible
If you find yourself reaching for !important, stop and investigate why the style is not applying. Open the browser DevTools (right-click → Inspect), find the element, and check the Computed tab to see which CSS rule is winning. Usually the fix is using cn() correctly, choosing the right component variant, or adjusting the stylesheet import order. If the style conflict spans multiple generated components and you cannot find the root cause, RapidDev's engineers have resolved CSS specificity battles across 600+ Lovable projects.
/* !important arms race — each override needs more !important */<div className="bg-white !important"> <Button className="!bg-red-500 !hover:bg-red-600 !text-white"> Delete </Button></div>{/* Fragile — breaks as soon as another style uses !important */}/* Proper fix — use cn() and component variants */<div className="bg-background"> <Button variant="destructive"> Delete </Button></div>{/* Uses the theme system — no !important needed */}Expected result: The destructive button renders with the correct red styling from the theme. No !important declarations clutter the codebase.
Complete code example
1import { cn } from "@/lib/utils";2import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";3import { Button } from "@/components/ui/button";4import { Badge } from "@/components/ui/badge";56interface StyledCardProps {7 title: string;8 description: string;9 status: "active" | "inactive" | "pending";10 className?: string;11 onAction?: () => void;12}1314// Example of conflict-free styling with cn() and variants15const StyledCard = ({ title, description, status, className, onAction }: StyledCardProps) => {16 // Map status to badge variants — no custom CSS needed17 const badgeVariant = {18 active: "default" as const,19 inactive: "secondary" as const,20 pending: "outline" as const,21 }[status];2223 return (24 <Card className={cn(25 "transition-shadow hover:shadow-md", // Default styles26 status === "inactive" && "opacity-60", // Conditional styles27 className // Allow parent to override — cn() resolves conflicts28 )}>29 <CardHeader className="flex flex-row items-center justify-between">30 <CardTitle className="text-lg">{title}</CardTitle>31 <Badge variant={badgeVariant}>{status}</Badge>32 </CardHeader>33 <CardContent className="space-y-4">34 <p className="text-muted-foreground">{description}</p>35 {onAction && (36 <Button37 variant={status === "active" ? "default" : "outline"}38 size="sm"39 onClick={onAction}40 >41 {status === "active" ? "Deactivate" : "Activate"}42 </Button>43 )}44 </CardContent>45 </Card>46 );47};4849export default StyledCard;Best practices to prevent this
- Always use cn() from @/lib/utils to merge Tailwind classes — it resolves conflicts using tailwind-merge
- Use shadcn/ui component variants (variant='destructive', variant='outline') before writing custom CSS overrides
- Use Design view → Themes tab for global color, font, and spacing changes instead of per-component overrides
- Never use !important in Tailwind classes — it creates an escalating specificity war that is hard to undo
- Pass className as the last argument to cn() so custom classes override defaults, not the other way around
- When customizing shadcn/ui components in src/components/ui/, always spread the className prop through cn()
- Check browser DevTools → Computed tab to see which CSS rule is actually winning when a style conflict occurs
- Keep global CSS in index.css or globals.css minimal — prefer Tailwind utilities and theme variables for all styling
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm having CSS conflicts in my Lovable (lovable.dev) project between my custom Tailwind classes and shadcn/ui component defaults. Here is the component where the conflict occurs: [paste your component code here] The issue: [describe which style is not applying — e.g., 'my bg-red-500 class is being overridden by the Button default background'] Please: 1. Explain why the CSS conflict is happening (specificity, class order, etc.) 2. Show the fix using cn() from @/lib/utils 3. Check if there's a shadcn/ui variant I should use instead of custom classes 4. Suggest whether I should modify the component in src/components/ui/ or use className overrides
I have CSS conflicts where my custom Tailwind classes are not applying to shadcn/ui components. Please review @src/components/ui/button.tsx and ensure the className prop is being merged using cn() as the last argument so custom classes can override defaults. Then check @src/components/[MyComponent].tsx to make sure all class merging uses cn() instead of template literals or string concatenation.
Frequently asked questions
What is the cn() function in Lovable projects?
cn() is a utility function at src/lib/utils.ts that merges Tailwind CSS class names using clsx and tailwind-merge. It resolves conflicts by keeping the last conflicting class. For example, cn('bg-red-500', 'bg-blue-500') returns 'bg-blue-500'.
Why is my custom Tailwind class not applying to a shadcn/ui component?
The component's default styles may have equal specificity and appear later in the stylesheet. Use cn() to merge your custom className with the defaults, or check if there is a component variant (like variant='destructive') that matches your desired style.
Should I use !important to fix CSS conflicts in Lovable?
No. Using !important creates a specificity arms race that makes future style changes extremely difficult. Instead, use cn() for class merging, component variants for common style changes, and Design view → Themes tab for global theme modifications.
How do I change the primary color across my entire Lovable app?
Open Design view (click + next to Preview) → Themes tab and change the primary color. This updates the CSS variable that all shadcn/ui components use. Every button, link, and badge using the primary color updates automatically.
Can I edit shadcn/ui component files directly?
Yes. The shadcn/ui components are fully yours — they live in src/components/ui/ and can be customized. Edit them in Dev Mode or ask Lovable to modify them. Just make sure you keep the cn() className merging pattern so custom overrides still work.
How do I handle dark mode CSS conflicts?
Lovable uses CSS variables for theme colors, which automatically switch between light and dark mode. Use theme-aware classes like bg-background, text-foreground, and border-border instead of hardcoded colors like bg-white or text-black.
What if I can't resolve CSS conflicts myself?
CSS specificity issues that span multiple generated components can be difficult to untangle. RapidDev's engineers have resolved styling conflicts across 600+ Lovable projects and can restructure your styles for consistency.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your issue.
Book a free consultation