Поддержка динамических тем оформления сайта

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Поддержка динамических тем оформления сайта
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Поддержка динамических тем оформления сайта

Динамическая смена темы — это не просто кнопка «тёмный/светлый режим». Это система управления дизайн-токенами, которая должна работать без мерцания при загрузке, корректно сохраняться между сессиями, уважать системные настройки пользователя и поддерживать произвольное количество тем без дублирования CSS.

Архитектура на CSS-переменных

Правильный фундамент — CSS Custom Properties. Вся палитра и типографика описываются через переменные, компоненты используют только переменные (никаких хардкодных #1a1a2e):

/* Базовая тема (светлая) */
:root {
  --color-bg-primary:    #ffffff;
  --color-bg-secondary:  #f5f5f5;
  --color-text-primary:  #1a1a1a;
  --color-text-muted:    #6b7280;
  --color-accent:        #3b82f6;
  --color-border:        #e5e7eb;
  --shadow-card:         0 1px 3px rgba(0,0,0,0.1);
}

/* Тёмная тема */
[data-theme="dark"] {
  --color-bg-primary:    #0f172a;
  --color-bg-secondary:  #1e293b;
  --color-text-primary:  #f1f5f9;
  --color-text-muted:    #94a3b8;
  --color-accent:        #60a5fa;
  --color-border:        #334155;
  --shadow-card:         0 1px 3px rgba(0,0,0,0.5);
}

/* Третья тема (пример: высокий контраст) */
[data-theme="high-contrast"] {
  --color-bg-primary:    #000000;
  --color-text-primary:  #ffffff;
  --color-accent:        #ffff00;
  --color-border:        #ffffff;
}

Смена темы — один setAttribute:

document.documentElement.setAttribute('data-theme', 'dark');

Переход мгновенный, без перезагрузки страницы, без JavaScript-перекраски каждого элемента.

Устранение FOUC (Flash of Unstyled Content)

Главная проблема: если тема загружается через React после гидрации — пользователь видит мерцание светлого экрана перед тёмной темой. Решение — инлайн-скрипт в <head>, который выполняется до рендера:

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

Этот скрипт синхронный и крошечный (~200 байт). Он выполняется немедленно, устанавливая правильную тему до рендера любого CSS.

React Context + хук

type Theme = 'light' | 'dark' | 'high-contrast' | 'system';

interface ThemeContextValue {
  theme: Theme;
  resolvedTheme: 'light' | 'dark' | 'high-contrast';
  setTheme: (theme: Theme) => void;
}

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

export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [theme, setThemeState] = useState<Theme>(() => {
    if (typeof window === 'undefined') return 'system';
    return (localStorage.getItem('theme') as Theme) || 'system';
  });

  const systemTheme = useMediaQuery('(prefers-color-scheme: dark)') ? 'dark' : 'light';
  const resolvedTheme = theme === 'system' ? systemTheme : theme;

  useEffect(() => {
    document.documentElement.setAttribute('data-theme', resolvedTheme);
  }, [resolvedTheme]);

  const setTheme = (newTheme: Theme) => {
    setThemeState(newTheme);
    localStorage.setItem('theme', newTheme);
  };

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

export const useTheme = () => {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error('useTheme must be inside ThemeProvider');
  return ctx;
};

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

const ThemeToggle: React.FC = () => {
  const { theme, setTheme } = useTheme();

  const options: { value: Theme; icon: React.ReactNode; label: string }[] = [
    { value: 'light',  icon: <SunIcon />,     label: 'Светлая' },
    { value: 'dark',   icon: <MoonIcon />,    label: 'Тёмная' },
    { value: 'system', icon: <MonitorIcon />, label: 'Системная' },
  ];

  return (
    <div className="theme-toggle" role="group" aria-label="Выбор темы">
      {options.map(opt => (
        <button
          key={opt.value}
          onClick={() => setTheme(opt.value)}
          aria-pressed={theme === opt.value}
          title={opt.label}
        >
          {opt.icon}
        </button>
      ))}
    </div>
  );
};

Плавный переход между темами

Без анимации смена темы выглядит резко:

*, *::before, *::after {
  transition:
    background-color 200ms ease,
    color 150ms ease,
    border-color 200ms ease,
    box-shadow 200ms ease;
}

Важный нюанс: эту transition нужно отключать на время начальной загрузки, иначе при возврате на страницу будет видна анимация с дефолтных цветов:

// Убираем transition на 1 кадр после загрузки
useEffect(() => {
  document.documentElement.classList.add('no-transition');
  requestAnimationFrame(() => {
    document.documentElement.classList.remove('no-transition');
  });
}, []);
.no-transition * { transition: none !important; }

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

Tailwind 4 поддерживает CSS-переменные нативно. Маппинг:

/* В tailwind.config или @theme */
@theme {
  --color-primary: var(--color-accent);
  --color-background: var(--color-bg-primary);
}

Для Tailwind 3 — darkMode: 'class' в конфиге, но лучше полностью перейти на CSS-переменные и не зависеть от dark: префиксов.

Пользовательские темы (colour picker)

Для продвинутых случаев — пользователь сам выбирает акцентный цвет:

const AccentPicker: React.FC = () => {
  const handleChange = (color: string) => {
    document.documentElement.style.setProperty('--color-accent', color);
    // Автоматически вычисляем hover-состояние
    document.documentElement.style.setProperty(
      '--color-accent-hover',
      adjustLightness(color, -10)
    );
    localStorage.setItem('accent-color', color);
  };

  return <input type="color" onChange={e => handleChange(e.target.value)} />;
};

Сроки

Задача Время
CSS-переменные + 2 темы (light/dark) 0.5 дня
FOUC-fix + React Context 0.5 дня
Переключатель + сохранение в localStorage 0.5 дня
Плавные переходы 0.5 дня
Дополнительные темы / colour picker 1–2 дня

Базовая реализация light/dark: 1.5–2 дня.