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

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Підтримка динамічних тем оформлення сайту
Середня
~2-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

Підтримка динамічних тем веб-сайту

Динамічна зміна теми — це не просто кнопка «темний/світлий режим». Це система управління дизайн-токенами, яка повинна працювати без мерцання при завантаженні, правильно зберігатися між сеансами, поважати системні налаштування користувача та підтримувати довільну кількість тем без дублювання 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 після гідрації — користувачі бачать мерцання світлого екрана перед темною темою. Рішення — inline-скрипт у <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: префіксів.

Користувацькі теми (Color 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 дня
Додаткові теми / color picker 1–2 дні

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