DEV Community

Cover image for Scalable UI State with React Context and Tailwind CSS
HexShift
HexShift

Posted on

Scalable UI State with React Context and Tailwind CSS

React is a leading library for building component-based UIs. Tailwind CSS integrates naturally by allowing styling through utility classes inline, while keeping logic and presentation neatly separated. But as state becomes complex—especially in large apps—it’s essential to scale responsibly. Using React Context alongside Tailwind offers a clean pattern for managing global UI state and styling consistently.


Why Use React Context with Tailwind?

  • Manage global UI state like themes, modals, notifications, sidebars
  • Automate class application across many components
  • Avoid prop drilling and tangled code
  • Keep styling declarative and centralized

Context offers a centralized state layer that works beautifully with Tailwind’s utility-first design.


Example: Sidebar Toggle State

Suppose your app has a sidebar that opens/closes:

// SidebarContext.js
import { createContext, useContext, useState } from 'react'

const SidebarContext = createContext()

export function SidebarProvider({ children }) {
  const [open, setOpen] = useState(false)
  const toggle = () => setOpen((o) => !o)
  return (
    <SidebarContext.Provider value={{ open, toggle }}>
      {children}
    </SidebarContext.Provider>
  )
}

export const useSidebar = () => useContext(SidebarContext)
Enter fullscreen mode Exit fullscreen mode

Use it in components:

import { useSidebar } from './SidebarContext'
function Sidebar() {
  const { open } = useSidebar()
  return (
    <div className={open ? 'w-64' : 'w-16'} /* plus other Tailwind classes */>
      {/* Sidebar content */}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Toggle anywhere in the tree:

function ToggleButton() {
  const { toggle } = useSidebar()
  return (
    <button onClick={toggle} className="p-2 bg-indigo-600 text-white rounded">
      Toggle Sidebar
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

Example: Notifications via Context

Notifications are another use case:

// NotificationContext.js
import { createContext, useContext, useState } from 'react'

const NotificationContext = createContext()

export function NotificationProvider({ children }) {
  const [msgs, setMsgs] = useState([])
  const add = (msg) => setMsgs((m) => [...m, msg])
  const remove = (i) => setMsgs((m) => m.filter((_, idx) => idx !== i))
  return (
    <NotificationContext.Provider value={{ msgs, add, remove }}>
      {children}
    </NotificationContext.Provider>
  )
}

export const useNotifications = () => useContext(NotificationContext)
Enter fullscreen mode Exit fullscreen mode

Rendering notifications:

import { useNotifications } from './NotificationContext'
function Alerts() {
  const { msgs, remove } = useNotifications()
  return (
    <div className="fixed top-4 right-4 space-y-2">
      {msgs.map((m, i) => (
        <div 
          key={i}
          className="bg-green-100 text-green-800 border border-green-300 rounded p-4">
          {m.text}
          <button onClick={() => remove(i)}>×</button>
        </div>
      ))}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Example: Theme Context (Dark Mode)

// ThemeContext.js
import { createContext, useContext, useState, useEffect } from 'react'

const ThemeContext = createContext()

export function ThemeProvider({ children }) {
  const [dark, setDark] = useState(false)
  useEffect(() => {
    document.documentElement.classList.toggle('dark', dark)
  }, [dark])
  const toggle = () => setDark((d) => !d)
  return (
    <ThemeContext.Provider value={{ dark, toggle }}>
      {children}
    </ThemeContext.Provider>
  )
}

export const useTheme = () => useContext(ThemeContext)
Enter fullscreen mode Exit fullscreen mode

Use classes like:

function PageLayout() {
  const { dark } = useTheme()
  return (
    <div className={dark ? 'bg-gray-900 text-white' : 'bg-white text-gray-900'}>
      {/* Page Content */}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Benefits of This Pattern

  • Separation of concerns: State lives in context, styling stays inline with Tailwind
  • Reusability: Context can be consumed anywhere
  • Maintainable UI: Shared visual logic via Tailwind avoids CSS drift
  • Scalable: Multi-step forms, dashboards, collaborative tools benefit most

Structure Example

Wrap your app:

import { SidebarProvider } from './SidebarContext'
import { NotificationProvider } from './NotificationContext'
import { ThemeProvider } from './ThemeContext'

function App() {
  return (
    <SidebarProvider>
      <NotificationProvider>
        <ThemeProvider>
          <PageLayout>
            <Sidebar />
            <ToggleButton />
            <Alerts />
            {/* other components */}
          </PageLayout>
        </ThemeProvider>
      </NotificationProvider>
    </SidebarProvider>
  )
}
Enter fullscreen mode Exit fullscreen mode

Want a Deep Dive?

If you're looking to structure large React projects with Tailwind and Context—covering utility strategies, component patterns, dark mode, and performance—you’ll love my 37-page PDF:

Mastering Tailwind at Scale: Architecture, Patterns & Performance

Grab it now for just $10.


With React Context and Tailwind CSS, you get clean, maintainable UI state and styling — built for scale and clarity.

Top comments (0)