Styling Modern React Applications 2025

Modern React development has fundamentally shifted from page-based thinking to component-based architecture, yet CSS practices haven't always evolved accordingly. This article explores contemporary approaches to styling React applications, comparing utility-first CSS (Tailwind), component libraries (shadcn/ui vs Ant Design), and CSS Modules. It also considers the role of AI-assisted development and generative UI contexts like MCP-UI and OpenAI’s AgentKit.

Key Recommendation: For most modern React applications in 2025, a combination of Tailwind CSS with shadcn/ui provides the optimal balance of developer experience, maintainability, and AI compatibility.

1. The Component-First Philosophy

Why Traditional CSS Falls Short

The fundamental issue with traditional CSS in modern React is a conceptual mismatch. We build components, not pages, yet we often style globally with semantic classes that attempt to describe content rather than purpose.

As Alex Kondov articulates in How to style a React Application, traditional semantic classes like .essay or .text-box create several problems:

  • Tight Coupling: CSS structure mirrors HTML structure, creating brittle dependencies
  • Reusability Challenges: Classes become either too specific to reuse or too generic to maintain semantic meaning
  • Scaling Issues: Each new variation requires architectural decisions about class hierarchy
  • Design Token Enforcement: Magical hardcoded values proliferate despite good intentions

Thinking in Components, Not Classes

The paradigm shift is simple but profound: you're not reusing CSS classes, you're reusing components. When you need a button styled consistently across your app, you create a Button component, not a .btn class. The component encapsulates both behavior and appearance.

This realization eliminates the artificial separation between markup and styles that made sense in the jQuery era but adds friction in component-based development.

2. Utility-First CSS: The Tailwind Approach

Why Utility CSS Works

Tailwind CSS embraces the tight coupling between components and their styles, but inverts the traditional approach:

Traditional Approach:

// Semantic class in HTML
<div className="user-card">
  <h2 className="user-card-title">John Doe</h2>
</div>

// Separate CSS file
.user-card { padding: 1rem; border: 1px solid gray; }
.user-card-title { font-size: 1.5rem; font-weight: bold; }

Utility-First Approach:

<div className="p-4 border border-gray-300 rounded">
  <h2 className="text-2xl font-bold">John Doe</h2>
</div>

Key Benefits

  • Fixed Complexity: Styling effort remains constant as the application grows. No risk of unintentional style conflicts.

  • Design Tokens Built-In: Using text-2xl or p-4 automatically enforces your design system. No need for code review vigilance on hardcoded values.

  • Colocation: Styles live with the markup, making changes faster and less error-prone. You never wonder "is this class used anywhere else?"

  • Readability Over Time: A year later, flex items-center gap-4 p-6 is immediately understandable without jumping to CSS files.

  • Predictable Performance: No cascading style conflicts, minimal CSS bundle size due to purging unused classes.

Handling Complexity

For components with significant conditional styling:

import { clsx } from 'clsx';

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
}

function Button({ variant = 'primary', size = 'md', disabled }: ButtonProps) {
  return (
    <button
      className={clsx(
        // Base styles
        'font-semibold rounded transition-colors',

        // Size variants
        {
          'px-3 py-1.5 text-sm': size === 'sm',
          'px-4 py-2 text-base': size === 'md',
          'px-6 py-3 text-lg': size === 'lg',
        },

        // Color variants
        {
          'bg-blue-600 hover:bg-blue-700 text-white': variant === 'primary',
          'bg-gray-200 hover:bg-gray-300 text-gray-900': variant === 'secondary',
          'bg-red-600 hover:bg-red-700 text-white': variant === 'danger',
        },

        // States
        disabled && 'opacity-50 cursor-not-allowed'
      )}
      disabled={disabled}
    >
      {children}
    </button>
  );
}

3. Component Libraries: shadcn/ui vs Ant Design

The Philosophical Divide

These two libraries represent fundamentally different approaches to component libraries:

Ant Design: Traditional component library

  • Pre-packaged, installable via npm
  • Opinionated design language (Alibaba's design system)
  • CSS-in-JS or Less for theming
  • 65+ production-ready components
  • Supports React, Angular, Vue

shadcn/ui: Component collection

  • Copy-paste components into your codebase
  • Built with Tailwind CSS and Radix UI primitives
  • You own the code completely
  • Minimal, customizable starting points
  • React-only (TypeScript-first)

Detailed Comparison

Aspect shadcn/ui Ant Design
Installation Copy components individually npm package dependency
Customization Full control - you own the code Theme configuration, CSS overrides
Bundle Size Only what you use Entire library (with tree-shaking)
Design Style Minimal, adaptable Enterprise-focused, polished
Learning Curve Requires Tailwind knowledge Well-documented API
Updates Manual (you copy new versions) Standard npm update
Accessibility Built on Radix UI primitives Good, but some reported issues
TypeScript Excellent, first-class Good support
AI Compatibility Excellent (simple structure) Moderate (complex API)

When to Choose Each

Choose shadcn/ui when:

  • Building a product requiring unique branding
  • You want maximum control and flexibility
  • Team is comfortable with Tailwind CSS
  • Prioritizing modern development practices
  • Working with AI coding assistants (Claude, Copilot, Cursor)
  • Building SaaS products or startups

Choose Ant Design when:

  • Building enterprise/internal tools quickly
  • Need extensive, production-tested components
  • Team prefers traditional component libraries
  • Design consistency more important than customization
  • Working with large teams requiring standardization
  • Need cross-framework support (React, Vue, Angular)

The shadcn/ui Advantage in 2025

shadcn/ui has gained significant momentum because it aligns with modern development practices:

  • Ownership: You control the components completely - no "magic" or black boxes
  • Simplicity: Components are straightforward React + Tailwind - easy to understand and modify
  • Composability: Built on Radix UI primitives, ensuring accessibility while allowing customization
  • Developer Experience: Works seamlessly with modern tooling (Vite, Next.js, TypeScript)
  • AI-Friendly: Simple, readable code structure makes it ideal for AI-assisted development

4. CSS Modules: When and Why?

What Are CSS Modules?

CSS Modules provide scoped CSS by automatically generating unique class names:

// Button.module.css
.button {
  padding: 0.5rem 1rem;
  background: blue;
}

// Button.tsx
import styles from './Button.module.css';

function Button() {
  return <button className={styles.button}>Click me</button>;
}

// Rendered as:
// <button class="Button_button__x7k3m">Click me</button>

The Modern Reality

CSS Modules are increasingly less relevant in 2025 for most React applications. Here's why:

  • Solved Problem: They solve style scoping, but Tailwind and component libraries already provide this
  • Build Step Complexity: They require configuration and tooling
  • Limited Reusability: Still suffer from the same reusability challenges as traditional CSS
  • Developer Experience: Require jumping between files
  • AI Assistance: Harder for AI to reason about styles split across files

When CSS Modules Still Make Sense

There are legitimate use cases:

  • Migration Path: Gradually modernizing a legacy codebase
  • Hybrid Approach: Using alongside Tailwind for complex, component-specific styles
  • Team Preference: When team is strongly opposed to utility classes
  • Specific Constraints: Projects with strict CSS-only requirements

Recommendation

For greenfield projects in 2025: Skip CSS Modules. Use Tailwind + shadcn/ui instead. For existing projects with CSS Modules: Consider them technical debt to eventually migrate away from, not expand.

5. AI-Assisted Development Considerations

Why This Matters

AI coding assistants (Claude, GitHub Copilot, Cursor) have become standard tools. Your styling approach significantly impacts AI effectiveness.

AI-Friendliness Comparison

Most AI-Friendly: Tailwind CSS

  • Simple, declarative utility classes
  • Self-documenting (classes describe exactly what they do)
  • No context switching between files
  • Easy for AI to reason about and modify
  • Consistent patterns across projects

Moderately AI-Friendly: shadcn/ui

  • Clear component structure
  • TypeScript-first makes intent obvious
  • Self-contained components
  • AI can easily suggest modifications

Least AI-Friendly: Complex CSS Architectures

  • CSS Modules require understanding file relationships
  • Ant Design's extensive API requires deep documentation knowledge
  • Custom CSS architectures need project-specific context

Practical Example

AI Prompt with Tailwind:

"Make this button larger and add a hover effect"

AI easily understands and modifies:

// From:
<button className="px-4 py-2 bg-blue-500">

// To:
<button className="px-6 py-3 bg-blue-500 hover:bg-blue-600 transition">

AI Prompt with CSS Modules:

"Make this button larger and add a hover effect"

AI must reason across files:

// Button.tsx - update className
// Button.module.css - modify .button class
// Need to understand both files and their relationship

Best Practices for AI-Assisted Development

  • Use Inline Styles (Tailwind): Keep everything in one file for AI context
  • TypeScript Everything: Helps AI understand component contracts
  • Clear Component Names: UserProfileCard not Card
  • Consistent Patterns: Follow established conventions
  • Documentation: Brief comments for complex logic

6. Generative & Adaptive UIs

The New Frontier

Generative UIs render dynamically based on user needs, often in sandboxed environments. Two major approaches:

MCP-UI (Model Context Protocol UI)

Architecture:

  • Server returns UI resources (HTML, React components)
  • Client renders in sandboxed iframe
  • Security-first design

Styling Implications:

import { createUIResource } from '@mcp-ui/server';

const interactiveForm = createUIResource({
  uri: 'ui://user-form/1',
  content: {
    type: 'externalUrl',
    iframeUrl: 'https://yourapp.com/form'
  }
});

Key Challenge: Iframe isolation means limited access to parent styles.

Solution: Self-contained components with Tailwind CDN:

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
  <div class="p-6 bg-white rounded-lg shadow-lg">
    <!-- Component content -->
  </div>
</body>
</html>

OpenAI AgentKit / Apps SDK

Architecture:

  • React components in iframe
  • Communication via window.openai API
  • Build step produces single JS bundle

Example Component:

import React, { useState, useEffect } from 'react';

export default function PizzaList() {
  const toolOutput = window.openai?.toolOutput;
  const [favorites, setFavorites] = useState<string[]>([]);

  useEffect(() => {
    if (toolOutput?.favorites) {
      setFavorites(toolOutput.favorites);
    }
  }, [toolOutput]);

  return (
    <div className="p-4 space-y-4">
      {toolOutput?.places?.map(place => (
        <div
          key={place.id}
          className="border rounded-lg p-4 hover:shadow-lg transition"
        >
          <h3 className="text-xl font-bold">{place.name}</h3>
          <p className="text-gray-600">{place.description}</p>
        </div>
      ))}
    </div>
  );
}

Styling Requirements:

  • Self-Contained: All styles inline (Tailwind perfect for this)
  • Responsive: Must work at various sizes (inline, PiP, fullscreen)
  • Theme-Aware: Respect host's theme (light/dark mode)
  • Minimal Bundle: Keep dependencies lean

Why Tailwind Dominates Here

For generative UIs, Tailwind CSS is nearly essential:

  • No External CSS Files: Everything inline
  • Predictable: No cascade issues from host environment
  • Responsive Built-In: md:, lg: prefixes handle layout modes
  • Small Bundle: Only includes used utilities
  • Theme Variables: Easy to respect host theme
  • AI Generation: AI can generate complete UI with just HTML + Tailwind

Example: Theme-Aware Component

function AdaptiveCard() {
  const theme = window.openai?.theme || 'light';

  return (
    <div className={`
      p-6 rounded-lg
      ${theme === 'dark'
        ? 'bg-gray-800 text-white'
        : 'bg-white text-gray-900'}
    `}>
      <h2 className="text-2xl font-bold mb-4">
        Adaptive Content
      </h2>
    </div>
  );
}

7. Practical Recommendations

For New React Projects (2025)

Recommended Stack:

  • Build Tool: Vite or Next.js
  • Styling: Tailwind CSS
  • Components: shadcn/ui
  • Icons: lucide-react (included with shadcn/ui)
  • Forms: React Hook Form + Zod
  • Utilities: clsx or cva (class-variance-authority)

Setup:

# Create new project
npm create vite@latest my-app -- --template react-ts
cd my-app

# Install Tailwind
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

# Install shadcn/ui
npx shadcn-ui@latest init

# Add components as needed
npx shadcn-ui@latest add button
npx shadcn-ui@latest add card

Component Architecture Pattern

// components/ui/button.tsx (from shadcn/ui)
// You own this code - modify freely

import { cva, type VariantProps } from "class-variance-authority";

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline: "border border-input hover:bg-accent hover:text-accent-foreground",
        ghost: "hover:bg-accent hover:text-accent-foreground",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {}

export function Button({ className, variant, size, ...props }: ButtonProps) {
  return (
    <button
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  );
}

For Enterprise Applications

Consider:

  • Ant Design if you need comprehensive, battle-tested components immediately
  • Tailwind + shadcn/ui if you can invest time in building your design system
  • Hybrid Approach: Ant Design for complex components (tables, forms), Tailwind for layout and custom components

For Generative/Adaptive UIs

Must-Have:

  • Tailwind CSS (self-contained styling)
  • Minimal dependencies
  • Bundle size optimization
  • Theme-aware design patterns

Architecture:

// Build a component library specifically for generative contexts
// components/adaptive/Card.tsx

interface CardProps {
  theme?: 'light' | 'dark';
  maxHeight?: number;
}

export function Card({ theme, maxHeight, children }: CardProps) {
  return (
    <div
      className={`
        p-6 rounded-lg shadow-lg
        ${theme === 'dark' ? 'bg-gray-800 text-white' : 'bg-white text-gray-900'}
      `}
      style={{ maxHeight }}
    >
      {children}
    </div>
  );
}

Migration Strategy (Legacy → Modern)

If you have existing CSS/CSS Modules:

  • Phase 1: Add Tailwind, use for new components
  • Phase 2: Gradually convert high-traffic components
  • Phase 3: Extract common patterns into shadcn/ui-style components
  • Phase 4: Remove old CSS once coverage is sufficient

Don't rewrite everything at once - parallel systems can coexist.

8. Anti-Patterns to Avoid

1. Mixing Too Many Approaches

// ❌ Bad - Three styling systems in one component
import styles from './card.module.css';
import { Button } from 'antd';

function Card() {
  return (
    <div className={`${styles.card} p-4 border`}>
      <Button type="primary">Click</Button>
    </div>
  );
}

2. Fighting the Framework

// ❌ Bad - Custom CSS overriding Tailwind
<div className="p-4" style={{ padding: '20px' }}>

// ✅ Good - Use Tailwind's scale
<div className="p-5">

3. Over-Engineering Abstraction

// ❌ Bad - Premature abstraction
const SPACING = {
  xs: 'p-1',
  sm: 'p-2',
  md: 'p-4',
  lg: 'p-6',
};

// ✅ Good - Use Tailwind directly until patterns emerge
<div className="p-4">

4. Inline Styles for Complex Logic

// ❌ Bad - Complex calc() in inline styles
<div style={{ width: 'calc(100% - 64px)' }}>

// ✅ Good - Use Tailwind or component variant
<div className="w-full pr-16">

9. Performance Considerations

Bundle Size Comparison

Typical Production Builds:

  • Tailwind CSS (purged): ~5-20 KB
  • shadcn/ui components: ~2-5 KB per component (you only bundle what you use)
  • Ant Design: ~200-500 KB (with tree-shaking)
  • CSS Modules: Depends on how much CSS you write

Optimization Tips

Tailwind Purging (automatic in modern setups):

// tailwind.config.js
module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  // Only classes used in these files are included
};

Component Lazy Loading:

const HeavyChart = lazy(() => import('./components/HeavyChart'));

CSS-in-JS Caution: Runtime CSS-in-JS (styled-components, Emotion without extraction) adds overhead. Avoid unless necessary.

Conclusion

The modern React styling landscape has consolidated around utility-first CSS, particularly Tailwind, combined with flexible component systems like shadcn/ui. This approach:

  • Aligns with component-based thinking
  • Scales predictably with application size
  • Works seamlessly with AI coding assistants
  • Supports generative/adaptive UI contexts
  • Provides excellent developer experience
  • Delivers optimal runtime performance

For most React developers in 2025, the winning combination is Tailwind CSS + shadcn/ui + TypeScript

This stack provides the right balance of productivity, maintainability, and flexibility for modern application development.


Additional Resources


Building scalable React applications with TypeScript is what I do. If you need production-grade components, real-time visualizations, or AI-integrated interfaces that actually ship, let's talk. Get in touch to discuss your requirements.