Реализация Theme Provider для динамической смены тем

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Theme Provider для динамической смены тем
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация Theme Provider для динамической смены тем

Тема сайта — это набор дизайн-токенов: цвета, типографика, отступы, радиусы, тени. Theme Provider — механизм, который делает эти токены доступными для всех компонентов и позволяет переключать их без перезагрузки страницы.

Два подхода: CSS Custom Properties (нативные CSS-переменные) или Context + CSS-in-JS. Первый — быстрее, проще, не зависит от фреймворка. Второй — больше гибкости при динамических токенах.

Подход на CSS Custom Properties

Тема живёт в CSS, JavaScript только переключает класс или атрибут на <html>:

/* themes.css */
:root,
[data-theme='light'] {
  --color-bg: #ffffff;
  --color-bg-secondary: #f8fafc;
  --color-text: #0f172a;
  --color-text-muted: #64748b;
  --color-primary: #2563eb;
  --color-primary-hover: #1d4ed8;
  --color-border: #e2e8f0;
  --color-shadow: rgb(0 0 0 / 0.08);

  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;

  --font-sans: 'Inter', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;
}

[data-theme='dark'] {
  --color-bg: #0f172a;
  --color-bg-secondary: #1e293b;
  --color-text: #f1f5f9;
  --color-text-muted: #94a3b8;
  --color-primary: #3b82f6;
  --color-primary-hover: #60a5fa;
  --color-border: #1e293b;
  --color-shadow: rgb(0 0 0 / 0.3);
}

[data-theme='sepia'] {
  --color-bg: #fdf6e3;
  --color-bg-secondary: #f5edd6;
  --color-text: #433422;
  --color-text-muted: #7c6a54;
  --color-primary: #c0392b;
  --color-primary-hover: #a93226;
  --color-border: #e8d5b0;
}

ThemeProvider

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

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

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

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

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' | 'sepia'>(() => {
    if (theme === 'system') return getSystemTheme()
    return theme as 'light' | 'dark' | 'sepia'
  }, [theme])

  // Применяем тему к <html>
  useEffect(() => {
    const root = document.documentElement
    root.setAttribute('data-theme', resolvedTheme)

    // Цвет строки браузера (Chrome Mobile)
    const metaThemeColor = document.querySelector('meta[name="theme-color"]')
    const colors: Record<string, string> = {
      light: '#ffffff',
      dark: '#0f172a',
      sepia: '#fdf6e3',
    }
    metaThemeColor?.setAttribute('content', colors[resolvedTheme])
  }, [resolvedTheme])

  // Реагируем на изменение системной темы
  useEffect(() => {
    if (theme !== 'system') return

    const mq = window.matchMedia('(prefers-color-scheme: dark)')
    const handler = () => {
      document.documentElement.setAttribute('data-theme', getSystemTheme())
    }

    mq.addEventListener('change', handler)
    return () => mq.removeEventListener('change', handler)
  }, [theme])

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

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

function useTheme(): ThemeContextValue {
  const ctx = useContext(ThemeContext)
  if (!ctx) throw new Error('useTheme должен использоваться внутри ThemeProvider')
  return ctx
}

Предотвращение FOUC (Flash of Unstyled Content)

При SSR или первой загрузке страница может мигнуть неправильной темой. Решение — inline-скрипт в <head>:

<!-- В <head>, до любых стилей -->
<script>
  (function () {
    var stored = localStorage.getItem('app-theme');
    var theme = stored && stored !== 'system' ? stored : (
      window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
    );
    document.documentElement.setAttribute('data-theme', theme);
  })();
</script>

В Next.js:

// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ru" suppressHydrationWarning>
      <head>
        <script
          dangerouslySetInnerHTML={{
            __html: `(function(){var s=localStorage.getItem('app-theme');var t=s&&s!=='system'?s:window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';document.documentElement.setAttribute('data-theme',t)})()`,
          }}
        />
      </head>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  )
}

Переключатель тем

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

  const labels: Record<ThemeId, string> = {
    system: 'Системная',
    light: 'Светлая',
    dark: 'Тёмная',
    sepia: 'Сепия',
  }

  return (
    <div role="radiogroup" aria-label="Тема оформления">
      {themes.map((t) => (
        <label key={t}>
          <input
            type="radio"
            name="theme"
            value={t}
            checked={theme === t}
            onChange={() => setTheme(t)}
          />
          {labels[t]}
        </label>
      ))}
    </div>
  )
}

Интеграция с Tailwind CSS

Tailwind поддерживает class-based и attribute-based dark mode:

// tailwind.config.ts
export default {
  darkMode: ['selector', '[data-theme="dark"]'],
  // Или для мультитемы через CSS-переменные:
  theme: {
    extend: {
      colors: {
        bg: 'var(--color-bg)',
        'bg-secondary': 'var(--color-bg-secondary)',
        primary: 'var(--color-primary)',
        text: 'var(--color-text)',
      },
    },
  },
}

Токены через TypeScript

Для строгой типизации токенов в CSS-in-JS:

const baseTheme = {
  colors: {
    primary: 'var(--color-primary)',
    background: 'var(--color-bg)',
    text: 'var(--color-text)',
  },
  radii: {
    sm: 'var(--radius-sm)',
    md: 'var(--radius-md)',
    lg: 'var(--radius-lg)',
  },
  fonts: {
    sans: 'var(--font-sans)',
    mono: 'var(--font-mono)',
  },
} as const

type Theme = typeof baseTheme

Что входит в работу

Определение токенов для всех тем в CSS, ThemeProvider с поддержкой системной темы, React-хук useTheme, предотвращение FOUC (inline-скрипт), переключатель тем, интеграция с Tailwind или CSS-in-JS.

Срок: 1 день.