본문 바로가기
2026년 5월 27일1분 읽기

shadcn/ui v3로 옮겼는데 Tailwind v4와 호환 깨짐 — Radix를 Base UI로 강제 변환?

김
김영삼
조회 551댓글 3

shadcn/ui를 v2에서 v3로 옮기려는데 Radix → Base UI 자동 마이그레이션이 깨끗하게 안 됩니다. 컴포넌트 42개 사용 중.

npx shadcn@3 migrate --from radix --to base-ui

위 명령 실행 후:

  • Dialog 8개 중 5개 — 자동 변환 OK
  • Tabs 12개 중 11개 — onValueChange → onChange 시그니처 못 맞춤
  • Tooltip — TooltipProvider 자동 제거 시 일부 hover 깨짐
  • 커스텀 wrapper 컴포넌트 — 전혀 처리 못함

또 Tailwind v4와 동시 사용 시 dark variant가 일부 안 먹어요. 단계적으로 옮기는 노하우 있으면 부탁드립니다.

댓글 3

김김영삼5개월 전

v3 자동 마이그레이션은 70~80% 성공률입니다. 완벽하지 않습니다. 저희도 같은 경험으로 정착한 5단계 패턴입니다.


1. 사전 — Tailwind v4 + dark variant 호환 먼저


Base UI는 Tailwind v4를 가정합니다. v3.x에서 옮기는 거면 Tailwind 먼저 v4로. 안 그러면 dark variant·container query 같이 깨집니다.


2. 컴포넌트 그룹별 단계 전환


한 번에 42개를 모두 옮기지 않습니다. 그룹별 — Form 계열 → Overlay 계열 → Navigation 계열 → Data Display 계열. 각 그룹 1~2주 운영하며 검증.


3. Tabs 시그니처 차이 — codemod 작성


// codemod: jscodeshift 스크립트
// onValueChange={(v) => setX(v)} → onChange={(_, v) => setX(v)}
module.exports = function(file, api) {
const j = api.jscodeshift
return j(file.source)
.find(j.JSXAttribute, { name: { name: 'onValueChange' } })
.forEach(p => {
p.node.name.name = 'onChange'
// arrow function 시그니처 수정
const arrow = p.node.value.expression
if (arrow.type === 'ArrowFunctionExpression') {
arrow.params = [j.identifier('_'), ...arrow.params]
}
})
.toSource()
}

npm run codemod:tabs로 일괄 변환.


4. 커스텀 wrapper는 한 곳에서 흡수


shadcn 컴포넌트를 한 번 더 wrapping한 커스텀 컴포넌트는 자동 마이그레이션이 못 합니다. wrapper의 props interface를 우리 도메인용으로 다시 정의:


// components/ui/AppDialog.tsx — 우리 도메인 wrapper
import * as Dialog from '@base-ui-components/react/dialog'

export function AppDialog({ open, onOpenChange, ...rest }) {
// 외부에서는 onOpenChange 그대로 쓰게 유지(우리 코드 영향 최소화)
return <Dialog.Root open={open} onOpenChange={onOpenChange}>{rest.children}</Dialog.Root>
}

이렇게 하면 우리 도메인 코드는 그대로, wrapper만 한 번 수정하면 됩니다.


5. Tooltip 함정 — controlled mode 강제


Base UI Tooltip은 controlled 우선. uncontrolled 사용하던 곳은 useState 추가 필요. 또 TooltipProvider는 자동 ctx로 옮겨졌으므로 명시 제거.


6. visual regression 테스트


Playwright + Storybook visual test 셋업. 그룹별 마이그레이션 후 스크린샷 diff. 미세한 spacing·hover 변경을 사람이 못 보는 경우 잡힙니다.


저희는 42개 컴포넌트를 6주 걸려 옮겼습니다. 한 번에 하지 마세요.

프프론트수진5개월 전

visual regression 테스트는 필수입니다. Chromatic이나 Argos 같은 서비스도 좋고, Playwright + GitHub Actions로 직접 구축해도 됩니다. 한 페이지에 컴포넌트 20개 있으면 사람 눈으로 변화 못 봅니다.

장장유나(guest)5개월 전
Base UI animation은 data-open, data-closed 속성으로 transition 정의합니다. Radix의 data-state="open"과 다릅니다. 기존 CSS animation 그대로 복붙하면 안 됨. shadcn v3 demo 보고 패턴 따라가는 게 가장 빠릅니다.
Ctrl+Enter로 등록