ReactInline Styles & Dynamic Styling

Inline Styles & Dynamic Styling

React's style prop accepts a plain JavaScript object. It is the lowest-overhead, most direct way to apply CSS to an element — no build step, no class name lookup, no external library. Inline styles are often dismissed as an anti-pattern, but they have a very specific sweet spot: truly dynamic, runtime-computed values that cannot be expressed as static class names.

Basic Syntax

The style prop takes a JavaScript object where CSS property names are written in camelCase and values are strings or numbers. Pixel values can be passed as plain numbers — React automatically appends px:

TSX
// Inline style object
function Badge({ color, children }: { color: string; children: React.ReactNode }) {
  return (
    <span
      style={{
        backgroundColor: color,     // computed at runtime
        color: 'white',
        padding: '2px 10px',         // string for non-pixel units
        borderRadius: 999,           // React appends 'px' → border-radius: 999px
        fontSize: 12,                // → font-size: 12px
        fontWeight: 600,
        display: 'inline-block',
      }}
    >
      {children}
    </span>
  )
}

// Usage — any valid CSS color works
<Badge color="#0070f3">New</Badge>
<Badge color="#16a34a">Active</Badge>
<Badge color={user.brandColor}>Custom</Badge>
Note
camelCase is required because CSS property names with hyphens are not valid JavaScript identifiers outside of strings. `background-color` → `backgroundColor`, `border-radius` → `borderRadius`, `z-index` → `zIndex`.
Dynamic Styles Based on State

The most common use case for inline styles is tying a visual property directly to a piece of state — sizes, positions, percentages, or user- chosen values:

TSX
import { useState } from 'react'

function ProgressBar({ value, max = 100 }: { value: number; max?: number }) {
  const pct = Math.min(100, Math.max(0, (value / max) * 100))

  return (
    <div
      style={{
        width: '100%',
        height: 8,
        background: '#e5e7eb',
        borderRadius: 4,
        overflow: 'hidden',
      }}
      role="progressbar"
      aria-valuenow={value}
      aria-valuemax={max}
    >
      <div
        style={{
          width: `${pct}%`,          // computed — cannot be a static class
          height: '100%',
          background: pct > 80 ? '#16a34a' : pct > 40 ? '#f59e0b' : '#dc2626',
          borderRadius: 4,
          transition: 'width 0.4s ease, background 0.4s ease',
        }}
      />
    </div>
  )
}

function Demo() {
  const [progress, setProgress] = useState(0)
  return (
    <div>
      <ProgressBar value={progress} />
      <button onClick={() => setProgress((p) => Math.min(100, p + 10))}>
        +10%
      </button>
    </div>
  )
}
CSS Custom Properties (Variables) with Inline Styles

CSS custom properties are the most powerful pattern for combining inline styles with class-based CSS. The inline style prop sets the variable value; the CSS class consumes it. This gives you dynamic values without sacrificing pseudo-selectors, media queries, or animations:

TSX
// The CSS class handles structure and animations
// The inline style passes the dynamic values as custom properties

function Avatar({
  src,
  size = 40,
  borderColor = '#0070f3',
}: {
  src: string
  size?: number
  borderColor?: string
}) {
  return (
    <img
      src={src}
      className="avatar"  // defined in a CSS Module or global stylesheet
      style={{
        '--avatar-size': `${size}px`,       // CSS custom property
        '--avatar-border': borderColor,
      } as React.CSSProperties}
    />
  )
}

CSS
/* avatar.css (or Avatar.module.css) */
.avatar {
  width: var(--avatar-size, 40px);
  height: var(--avatar-size, 40px);
  border-radius: 50%;
  border: 2px solid var(--avatar-border, #d1d5db);
  object-fit: cover;
  transition: border-color 0.2s ease;
}

.avatar:hover {
  /* pseudo-selectors still work — impossible with pure inline styles */
  filter: brightness(1.05);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--avatar-border) 30%, transparent);
}
Tip
Casting the style object `as React.CSSProperties` is necessary in TypeScript when you set custom properties (--var-name). The type only knows about standard CSS properties, not custom ones. The cast tells TypeScript to trust you.
Building an Animated Color Picker

Here is a more complete example: a color swatch picker where the preview is driven entirely by inline styles because the color value is user input:

TSX
import { useState } from 'react'

const PRESET_COLORS = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#8b5cf6', '#ec4899']

function ColorPicker({ onChange }: { onChange?: (color: string) => void }) {
  const [selected, setSelected] = useState(PRESET_COLORS[3])

  function pick(color: string) {
    setSelected(color)
    onChange?.(color)
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      {/* Preview */}
      <div
        style={{
          width: 64,
          height: 64,
          borderRadius: 12,
          background: selected,
          boxShadow: `0 4px 12px ${selected}66`,
          transition: 'background 0.2s ease, box-shadow 0.2s ease',
        }}
      />

      {/* Swatches */}
      <div style={{ display: 'flex', gap: 8 }}>
        {PRESET_COLORS.map((color) => (
          <button
            key={color}
            onClick={() => pick(color)}
            style={{
              width: 28,
              height: 28,
              borderRadius: '50%',
              background: color,
              border: selected === color ? '3px solid #111' : '2px solid transparent',
              outline: selected === color ? `2px solid ${color}` : 'none',
              outlineOffset: 2,
              cursor: 'pointer',
              transition: 'transform 0.1s ease',
              transform: selected === color ? 'scale(1.15)' : 'scale(1)',
            }}
            aria-label={`Pick color ${color}`}
          />
        ))}
      </div>

      {/* Freeform input */}
      <input
        type="color"
        value={selected}
        onChange={(e) => pick(e.target.value)}
        style={{ width: 40, height: 32, cursor: 'pointer', borderRadius: 4 }}
      />
    </div>
  )
}
Limitations of Inline Styles

Feature

Inline styles

Workaround

Pseudo-selectors (:hover, :focus)

Not supported

Use CSS class with state toggle or CSS custom props

Media queries

Not supported

Use CSS class or JS window resize listener

CSS animations / @keyframes

Not supported

Use CSS class + CSS custom prop for values

Pseudo-elements (::before)

Not supported

Must use CSS class

CSS variables consumption

Must use var()

Works fine as a pattern

Performance (many updates)

Each update diffs the object

Memoize with useMemo for stable objects

Combining Inline Styles with CSS Modules

The gold standard is: use a CSS Module class for everything that is static (layout, typography, pseudo-selectors, animations), and use inline styles only for the values that change at runtime:

TSX
import styles from './Tooltip.module.css'

interface TooltipProps {
  text: string
  x: number   // cursor position — runtime computed
  y: number
  visible: boolean
}

function Tooltip({ text, x, y, visible }: TooltipProps) {
  return (
    <div
      className={styles.tooltip}          // handles border-radius, shadow, typography
      style={{
        left: x + 12,                     // dynamic — cursor position
        top: y - 8,
        opacity: visible ? 1 : 0,         // could also be a CSS class toggle
        pointerEvents: visible ? 'auto' : 'none',
      }}
    >
      {text}
    </div>
  )
}
Performance Considerations

React compares the previous and next style objects on every render. For components that re-render frequently (e.g., inside animations), create the style object outside the component or memoize it so React gets a stable reference:

TSX
import { useMemo } from 'react'

function AnimatedNode({ x, y, color }: { x: number; y: number; color: string }) {
  // Memoize — only recomputes when x, y, or color changes
  const style = useMemo(
    () => ({
      position: 'absolute' as const,
      left: x,
      top: y,
      background: color,
      width: 12,
      height: 12,
      borderRadius: '50%',
    }),
    [x, y, color],
  )

  return <div style={style} />
}
  • Use inline styles for truly dynamic runtime values: positions, percentages, user-chosen colors

  • Use CSS custom properties when you also need pseudo-selectors or animations on the same element

  • Combine with CSS Modules — classes for structure, inline for dynamics

  • Memoize style objects in hot render paths to avoid unnecessary React object diffs

  • Avoid inline styles for static values — every static inline style is a missed caching opportunity for the browser

Warning
Never store sensitive information as a CSS custom property value via inline styles. CSS custom properties are readable by any script on the page and in browser DevTools.