Слогер Создать блог
Разработка

Почему иконки пропадают в тёмной теме: разбор currentColor, SVG и React

Иконка с fill="#000000" отлично живёт на светлом фоне, но исчезает в тёмном. Разбираем, почему так происходит и как перевести иконки на currentColor без боли.

Экспортировали иконку из Figma, воткнули в приложение, всё работает. Потом добавили тёмную тему — и половина тулбара исчезла. CSS не сломался. Просто в SVG остался fill="#000000", и никто не связал цвет с переменной или currentColor.

Это не проблема «иконки плохие», а проблема контракта. SVG, экспортированный из дизайн-инструмента, — это краска на холсте. Приложению нужен глиф, который наследует цвет. Разница принципиальная.

В чём обман экспорта

Дизайн-инструменты почти всегда отдают файл с жёсткими цветами. #000 — это не «значение по умолчанию», а осознанный выбор темы. Та же беда с #111, #1A1A1A, black и «почти чёрными» серыми из автоэкспорта. Прежде чем мержить, поищи их в файле.

Три контракта иконки

Выберите один. Смешение — это то, от чего гниют иконные системы.

  • Paint — фиксированные цвета в файле. Для иллюстраций и логотипов с настоящими фирменными оттенками.
  • Glyph — только форма; цвет — из CSS или пропсов. Для навигации, кнопок, инпутов.
  • Bitmap handoff — PNG/WebP для писем, презентаций или CMS, которая не переваривает SVG.

UI-элементам почти всегда нужно быть глифами. Для этого и существует currentColor.

Рабочая форма React-компонента

Вот форма, которая реально работает. В корне — viewBox, fill="none", aria-hidden и {...props}. Внутри — path со stroke="currentColor" и strokeWidth={1.5}. Всё.

  1. currentColor — fill или stroke следуют за цветом текста. Один источник правды с темой.
  2. {...props} на корневом svg — вызывающий код сам задаёт className, размер, тесты и a11y-переопределения, не редактируя файл.
  3. viewBox остаётся — width и height становятся настройками раскладки, а не геометрией.
  4. aria-hidden по умолчанию — иконка декоративная; доступное имя получает кнопка или ссылка рядом.

Если Figma оставила внутри

, удалите его, когда у контрола уже есть имя — иначе скринридеры объявят дважды.

Ловушка со stroke и fill: одни экспорты используют fill, другие stroke, третьи смешивают. Ставьте currentColor именно на тот атрибут, который реально рисует. Оставить fill="#000" на «stroke-иконке» — классическая тихая ошибка на ревью.

Тёмная тема — это цвет, а не файлы

Неправильный фикс: завести icon-dark.svg и icon-light.svg. Правильный: один глиф, тема через CSS. Контекст вроде text-slate-700 dark:text-slate-200 на навигации — и иконка перекрасится сама.

Многоцветные продуктовые логотипы — это paint, не глиф. Не тащите их через currentColor. Оставьте статичным SVG или .

React Native: та же идея, другой host

React Native не рендерит HTML-SVG. Нужен react-native-svg, и важный нюанс: числа, а не строки. width="24" и width={24} — разные вещи, и порты из веба часто забывают про это.

Paste-JSX vs SVGR — выбирайте по объёму, а не по идеологии

Ни один вариант не «профессиональнее». Просто разные ситуации.

КритерийPaste-JSXSVGR в бандлере
Когда братьРазовая иконка, дизайн-хендофф, общий превьюДесятки SVG в репозитории еженедельно
КонфигурацияНе трогаем Vite/webpack/NextОдин раз настраиваем бандлер
React NativeСразу можно получить JSX для RNНужен дополнительный плагин или отдельный пайплайн

В любом случае проверьте три вещи в выводе конвертера: kebab-case стал JSX-свойством, {...props} на корне, id градиентов и клипов уникальны. Не ждите, что конвертер сам придумает currentColor или разрулит все конфликты id — это ваша работа на ревью.

Цена в бандле

Иконка-компонент — это JavaScript в бандле. 40 иконок тулбара — обычно норм. Одна иллюстрация на 200 КБ, заинлайненная в JSX, — налог, который не окупается.

Правило: для интерфейса — компонент с currentColor. Для маркетинга, писем и CMS — PNG или статический SVG. next/image — не тот инструмент для интерактивных иконок, он не даст пропсами управлять цветом.

Next.js App Router

Иконки без хуков — валидные серверные компоненты: просто JSX. Клади файлы в components/icons/, чтобы неиспользуемые выпиливались. Не делайте общий баррел icons.tsx, который тащит всё в граф.

Чек-лист на 60 секунд

  1. Поиск #000 / #fff / black / white — должны стать currentColor или пропсами.
  2. currentColor стоит на том атрибуте, который реально рисует: fill или stroke.
  3. viewBox на месте.
  4. Корневой svg распространяет {...props}.
  5. Декоративная иконка — aria-hidden; значимая — имя на контроле, не на иконке.
  6. id градиентов и клипов уникальны в пределах файла.
  7. В React Native пропсы — числа, не строки.
  8. Большая иллюстрация — статический ассет, а не компонент.

Проблема упирается не в инструмент, а в то, как вы договариваетесь с собственными иконками. Один раз настраиваете форму — и тёмная тема перестаёт быть сюрпризом.

По материалам: dev. Текст переработан редакцией Слогера.

← На главную

Рекламное место — Конец поста
Реклама · Слогер

Комментарии (0)

Войдите, чтобы комментировать.

Пока нет комментариев. Будьте первым.