· 6 min read

Styling Tricks in React: Making Your UI Stand Out

A practical, in-depth guide to styling React apps: choosing between CSS-in-JS, utility-first, and modular CSS; responsive and modern layout techniques; dynamic, state-driven styling; animations; accessibility and performance tips to make your UI pop.

Introduction

Styling in React is no longer an afterthought - it’s part of the user experience. Whether you’re shipping a design system or a single landing page, the right styling approach helps your UI feel polished and work well across devices. This article walks through practical styling strategies: from CSS-in-JS and utility-first approaches to responsive techniques and state-based styling patterns for dynamic UIs.

Contents

  • Why styling choices matter
  • CSS-in-JS: styled-components and Emotion (with examples)
  • Utility-first and atomic CSS: Tailwind
  • CSS Modules and classic approaches
  • Responsive techniques: clamp(), grid, container queries
  • State-based styling: props, classes, animations
  • Accessibility and reduced-motion
  • Performance, SSR and best practices

Why styling choices matter

Different approaches change how you organize styles, compose components and ship to production:

  • Maintainability: Are styles co-located with components or kept in separate files?
  • Theming: How easy is it to swap colors, typography and dark mode?
  • Bundle & runtime cost: Does the approach add runtime JS for styles?
  • Developer ergonomics: Do you prefer explicit CSS vs. small utility classes?

Choosing isn’t about a single “best” solution, it’s about what matches your team and product constraints.

CSS-in-JS (styled-components, Emotion)

CSS-in-JS co-locates styles with components and supports dynamic styling via props. Two popular libraries are styled-components and Emotion.

Example with styled-components

// Button.jsx
import styled from 'styled-components';

const Button = styled.button`
  padding: 0.6rem 1rem;
  border-radius: 8px;
  font-weight: 600;
  background: ${props =>
    props.primary ? 'linear-gradient(90deg,#667EEA,#764BA2)' : '#fff'};
  color: ${props => (props.primary ? '#fff' : '#333')};
  border: 1px solid rgba(0, 0, 0, 0.08);
  box-shadow: ${props =>
    props.primary ? '0 6px 18px rgba(102,126,234,0.25)' : 'none'};

  &:hover {
    transform: translateY(-2px);
  }
  &:active {
    transform: translateY(0);
  }
`;

export default Button;

Why this is useful:

  • Props-driven styling is expressive and matches React patterns.
  • Theming is built-in (ThemeProvider) - change colors globally.

Emotion and the css prop

Emotion supports a similar styled API plus a convenient css prop for quick styles. It also offers zero-runtime builds via Babel macros, if you need low runtime cost.

Utility-first CSS (Tailwind)

Tailwind CSS provides low-level utility classes (like p-4, text-lg, grid) that you compose in markup. This can make visual changes fast and reduce CSS file size in production due to purging.

Example

// Card.jsx (using Tailwind)
export default function Card({ title, children }) {
  return (
    <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
      <h3 className="text-lg font-semibold text-gray-900 dark:text-white">
        {title}
      </h3>
      <div className="mt-3 text-gray-600 dark:text-gray-300">{children}</div>
    </div>
  );
}

Why Tailwind:

  • High velocity for UI iteration.
  • Consistent spacing & typography system encoded in utilities.
  • Great ecosystem for design systems.

CSS Modules and classic CSS

If you prefer standard CSS but want to avoid global namespace collisions, CSS Modules are a simple compromise. Import a .module.css and class names are local by default.

/* Card.module.css */
.card {
  background: white;
  border-radius: 8px;
  padding: 16px;
}
.title {
  font-weight: 700;
}
import styles from './Card.module.css';

export default function Card({ title, children }) {
  return (
    <div className={styles.card}>
      <h3 className={styles.title}>{title}</h3>
      <div>{children}</div>
    </div>
  );
}

Responsive techniques and modern layout

Fluid typography with clamp()

Use CSS clamp() to make text scale between two sizes based on viewport width:

h1 {
  font-size: clamp(1.5rem, 2.5vw + 1rem, 3rem);
}

See MDN: clamp()

Layout with Grid and Flexbox

  • Flexbox is great for linear layouts (rows/columns).
  • CSS Grid is powerful for complex two-dimensional layouts.

Responsive container queries

Container queries let components adapt to the size of their container (not the whole viewport). This is excellent for reusable components in different layout contexts.

Basic example (syntax may vary by browser support):

.card {
  container-type: inline-size;
}

@container (min-width: 480px) {
  .card {
    grid-template-columns: 1fr 200px;
  }
}

Read more: Container Queries on MDN

Responsive images and aspect-ratio

  • Use the CSS property aspect-ratio to preserve media proportions.
  • Use srcset and sizes for responsive images to save bandwidth.

State-based styling: making UIs feel alive

Class toggling (simple)

The simplest way to reflect state in style is toggling classes.

import { useState } from 'react';

function ToggleCard() {
  const [open, setOpen] = useState(false);
  return (
    <div className={open ? 'card card--open' : 'card'}>
      <button onClick={() => setOpen(o => !o)}>Toggle</button>
      {open && <div className="details">Expanded content</div>}
    </div>
  );
}

Dynamic styles with CSS-in-JS

As shown earlier, CSS-in-JS lets you drive styles from props directly. This is excellent for themes, responsive props (size variants), and interactions.

Inline styles (when to use)

Inline styles (style={{ ... }}) are fine for small, runtime-driven style values (e.g., dynamic transform or color computed from data), but avoid using them for pseudo-classes (:hover) or large style sets.

Animating states: Framer Motion

For smooth, accessible animations consider Framer Motion. It gives declarative physics-based animation primitives.

import { motion } from 'framer-motion';

export default function ExpandingCard({ isOpen }) {
  return (
    <motion.div
      animate={{ height: isOpen ? 'auto' : 0, opacity: isOpen ? 1 : 0 }}
      transition={{ duration: 0.3 }}
      style={{ overflow: 'hidden' }}
    >
      <div>Expandable content</div>
    </motion.div>
  );
}

Accessibility and reduced motion

Respect users’ preferences and keep animations optional:

@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
  }
}

See MDN: prefers-reduced-motion

Other accessibility points:

  • Maintain sufficient color contrast.
  • Keep visible focus states for keyboard users.
  • Avoid content shifts that confuse screen readers.

Theming and CSS variables

CSS variables (custom properties) make runtime theming easy. Use them for color palettes, spacing scales and typographic scales.

:root {
  --bg: 255 255 255; /* store as RGB for easy alpha */
  --text: 17 24 39;
  --accent: 102 126 234;
}

.card {
  background-color: rgb(var(--bg));
  color: rgb(var(--text));
}

/* dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: 15 23 42;
    --text: 226 232 240;
  }
}

Combining CSS variables with CSS-in-JS or utility-first systems creates flexible theming with low runtime overhead.

Performance & SSR considerations

  • Server-side rendering + CSS-in-JS: Ensure your library supports SSR extraction (styled-components, Emotion both do).
  • Avoid recreating inline style objects on every render - memoize objects or use style values (numbers, strings).
  • Purge unused Tailwind classes in production to keep CSS small.

Practical tips and patterns

  1. Co-locate styles for small components; centralize for theme tokens.
  2. Use utility classes for fast prototypes; refactor into components for repeated patterns.
  3. Prefer CSS variables for theme values used across multiple components.
  4. Use clamp() for fluid typography and min/max for responsive spacing.
  5. Respect motion preferences - provide reduced-motion alternatives.
  6. For animations, prefer transform and opacity for GPU-accelerated transitions.
  7. When using CSS-in-JS, prefer static styles where possible and only use props for genuinely dynamic rules.

Example: A small, dynamic card with theme and responsive behavior

// DynamicCard.jsx (styled-components + CSS variables)
import styled from 'styled-components';

const Card = styled.article`
  --card-bg: var(--card-bg, 255 255 255);
  background: rgb(var(--card-bg));
  border-radius: 12px;
  padding: clamp(12px, 2vw, 24px);
  transition:
    box-shadow 180ms ease,
    transform 180ms ease;

  &:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
  }
`;

export default function DynamicCard({ children }) {
  return <Card>{children}</Card>;
}

Further reading and references

Checklist: Make your UI stand out (practical)

  • Use a consistent spacing & typography scale.
  • Add subtle elevation/shadow and interaction cues.
  • Use fluid sizes (clamp) and container-aware components.
  • Make state changes feel responsive with small, performant animations.
  • Respect accessibility: contrast, focus, reduced motion.
  • Profile CSS and JS bundle size - aim for a good shipping experience.

Conclusion

Styling React apps is a combination of art and engineering. Choose tools that match team workflows, prefer predictable systems (design tokens, CSS variables) for theming, and use modern layout and state-driven styling to make interfaces feel responsive and delightful. With the right balance of CSS-in-JS, utility classes, and performance-minded patterns you can build UIs that look great, adapt fluidly, and remain maintainable.

Back to Blog

Related Posts

View All Posts »

The Future of Tailwind CSS: Predictions for 2024 and Beyond

A forward-looking analysis of how trends in web design and browser capabilities - from container queries and cascade layers to AI-driven tooling and design tokens - will shape Tailwind CSS and the wider utility-first movement in 2024 and beyond.

React and State Management: Expert Tricks for Efficiency

A strategic, in-depth guide to state management patterns and advanced tricks in React. Covers local vs global state, server-state libraries, normalization, selectors, immutability helpers, concurrency techniques, and practical code patterns to reduce re-renders and simplify data flow.