Theme Provider for Dynamic Theme Switching

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1043
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815

Implementing Theme Provider for Dynamic Theme Switching

Theme is a set of design tokens: colors, typography, spacing, radius, shadows. Theme Provider is a mechanism that makes these tokens available to all components and allows switching them without page reload.

Two approaches: CSS Custom Properties (native CSS variables) or Context + CSS-in-JS. First is faster, simpler, framework-independent. Second provides more flexibility for dynamic tokens.

CSS Custom Properties approach

Theme lives in CSS, JavaScript only switches class or attribute on <html>:

/* themes.css */
:root,
[data-theme='light'] {
  --color-bg: #ffffff;
  --color-text: #0f172a;
  --color-primary: #2563eb;
  --radius-md: 8px;
  --font-sans: 'Inter', system-ui, sans-serif;
}

[data-theme='dark'] {
  --color-bg: #0f172a;
  --color-text: #f1f5f9;
  --color-primary: #3b82f6;
}

ThemeProvider

type ThemeId = 'light' | 'dark' | 'system'

interface ThemeContextValue {
  theme: ThemeId
  resolvedTheme: 'light' | 'dark'
  setTheme: (theme: ThemeId) => void
  themes: ThemeId[]
}

const ThemeContext = createContext<ThemeContextValue | null>(null)

const STORAGE_KEY = 'app-theme'
const THEMES: ThemeId[] = ['system', 'light', 'dark']

function getSystemTheme(): 'light' | 'dark' {
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}

function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setThemeState] = useState<ThemeId>(() => {
    if (typeof window === 'undefined') return 'system'
    return (localStorage.getItem(STORAGE_KEY) as ThemeId) ?? 'system'
  })

  const resolvedTheme = useMemo<'light' | 'dark'>(() => {
    if (theme === 'system') return getSystemTheme()
    return theme as 'light' | 'dark'
  }, [theme])

  // Apply theme to <html>
  useEffect(() => {
    const root = document.documentElement
    root.setAttribute('data-theme', resolvedTheme)

    // Browser chrome color (Chrome Mobile)
    const metaThemeColor = document.querySelector('meta[name="theme-color"]')
    const colors: Record<string, string> = {
      light: '#ffffff',
      dark: '#0f172a',
    }
    metaThemeColor?.setAttribute('content', colors[resolvedTheme])
  }, [resolvedTheme])

  const setTheme = (newTheme: ThemeId) => {
    setThemeState(newTheme)
    localStorage.setItem(STORAGE_KEY, newTheme)
  }

  return (
    <ThemeContext.Provider value={{ theme, resolvedTheme, setTheme, themes: THEMES }}>
      {children}
    </ThemeContext.Provider>
  )
}

export const useTheme = () => {
  const context = useContext(ThemeContext)
  if (!context) throw new Error('useTheme must be used within ThemeProvider')
  return context
}

Usage in components

function ThemeSwitcher() {
  const { theme, themes, setTheme } = useTheme()

  return (
    <select value={theme} onChange={(e) => setTheme(e.target.value as ThemeId)}>
      {themes.map((t) => (
        <option key={t} value={t}>{t}</option>
      ))}
    </select>
  )
}