Экспортировали иконку из 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}. Всё.
- currentColor — fill или stroke следуют за цветом текста. Один источник правды с темой.
- {...props} на корневом svg — вызывающий код сам задаёт className, размер, тесты и a11y-переопределения, не редактируя файл.
- viewBox остаётся — width и height становятся настройками раскладки, а не геометрией.
- 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-JSX | SVGR в бандлере |
|---|---|---|
| Когда брать | Разовая иконка, дизайн-хендофф, общий превью | Десятки 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 секунд
- Поиск #000 / #fff / black / white — должны стать currentColor или пропсами.
- currentColor стоит на том атрибуте, который реально рисует: fill или stroke.
- viewBox на месте.
- Корневой svg распространяет {...props}.
- Декоративная иконка — aria-hidden; значимая — имя на контроле, не на иконке.
- id градиентов и клипов уникальны в пределах файла.
- В React Native пропсы — числа, не строки.
- Большая иллюстрация — статический ассет, а не компонент.
Проблема упирается не в инструмент, а в то, как вы договариваетесь с собственными иконками. Один раз настраиваете форму — и тёмная тема перестаёт быть сюрпризом.
Комментарии (0)
Войдите, чтобы комментировать.
Пока нет комментариев. Будьте первым.