Урок 18. Частые ошибки

📁 Раздел: shadcn/ui ⏱️ Время изучения: ~55 мин 🎯 Сложность: Продвинутая

⚡ Кратко

Забытый cn(), замена примитива на div, обёртки вместо правки, потерянные data-slot, злоупотребление !.

Ошибка 1. className не доходит до элемента

Как ломается: <Button className="w-full"> не растягивает кнопку.

❌ Пропущен className

function Button({ variant, size, ...props }) {
  return <button className={cn(buttonVariants({ variant, size }))} {...props} />
}

✅ className участвует в слиянии

function Button({ className, variant, size, ...props }) {
  return <button className={cn(buttonVariants({ variant, size, className }))} {...props} />
}

Почему коварно: в первом варианте className попадёт в ...props и перезапишет весь атрибут целиком — кнопка потеряет вообще все базовые стили.

Ошибка 2. Замена примитива на обычный тег

Как ломается: визуально то же самое, но пропадает поведение: диалог не закрывается по Esc, меню не ходит по стрелкам, фокус уходит на страницу под окном.

Как правильно: менять оформление — сколько угодно. Менять слой поведения — только если вы готовы реализовать его сами и протестировать с клавиатурой и скринридером.

Ошибка 3. Обёртка вместо правки

❌ Обёртка поверх компонента

function BrandButton(props) {
  return (
    <div className="[&_button]:bg-brand [&_button]:text-white">
      <Button {...props} />
    </div>
  )
}

✅ Вариант внутри компонента

// в buttonVariants
brand: "bg-brand text-brand-foreground hover:opacity-90"

Почему: обёртка добавляет лишний узел в DOM, ломает раскладку (лишний div во flex-контейнере) и делает поведение непредсказуемым. Файл ваш — правьте его.

Ошибка 4. Потерянные data-slot при рефакторинге

Как ломается: карточка перестаёт убирать нижний отступ при наличии футера, потому что селектор has-data-[slot=card-footer] больше ничего не находит.

Как правильно: относитесь к data-slot как к части контракта компонента. Переименовали — проверьте все селекторы, которые на него ссылаются.

Ошибка 5. Злоупотребление модификатором !

❌ Пробиваем всё подряд

<Button className="bg-emerald-600! px-8! text-white!">Кнопка</Button>

✅ Обычные классы: cn() и так их применит

<Button className="bg-emerald-600 px-8 text-white">Кнопка</Button>

Почему: если cn() настроен правильно, конфликтующие базовые классы просто удаляются. Появление ! обычно означает, что где-то потерялся cn().

Ошибка 6. Копирование компонента вместо варианта

Понадобилась кнопка с другим фоном — и в проекте появляется button-2.tsx. Через полгода их пять, и правка фокуса не доезжает до четырёх из них. Новый внешний вид — это вариант, а не новый компонент.